haskelltypeclass

Avoiding Overlapping Instances with Closed Type Families (GHC/AdvancedOverlap)


Any pointers on how to make the closed type family example from the Haskell Wiki compile?

GHC/AdvancedOverlap (Solution 3)

Here is the version I tried:

{-# LANGUAGE 
    DataKinds,
    FlexibleInstances,
    MultiParamTypeClasses,
    TypeFamilies,
    UndecidableInstances #-}

data HTrue
data HFalse

type family ShowPred a where
  ShowPred Int   = HTrue
  ShowPred Bool  = HTrue
  ShowPred [a]   = ShowPred a
  ShowPred a     = HFalse

class Print a where
  print :: a -> IO ()

instance (ShowPred a ~ flag, Print' flag a) => Print a where
  print = print' (undefined :: flag)

class Print' flag a where
  print' :: flag -> a -> IO ()

instance Show a => Print' HTrue a where
  print' _ x = putStrLn (show x)

instance Print' flag a where
  print' _ _ = putStrLn "No show method"

However, this still gives me:

Overlapping instances for Print' flag0 a.

(GHC 8.0.1 is the version I am using.)

Edit: Defining the second instance as Print' HFalse a results in:

Could not deduce (Print' flag0 a) arising from a use of print' from the context: (ShowPred a ~ flag, Print' flag a).

Edit: Here is the example with all the corrections. All credit goes to @dfeuer.

{-# LANGUAGE 
    DataKinds,
    FlexibleInstances,
    MultiParamTypeClasses,
    ScopedTypeVariables,
    TypeFamilies,
    UndecidableInstances #-}

import Prelude hiding (print)

import Data.Proxy

data HTrue
data HFalse

type family ShowPred a where
  ShowPred Int   = HTrue
  ShowPred Bool  = HTrue
  ShowPred [a]   = ShowPred a
  ShowPred a     = HFalse

class Print a where
  print :: a -> IO ()

instance (ShowPred a ~ flag, Print' flag a) => Print a where
  print = print' (Proxy :: Proxy flag)

class Print' flag a where
  print' :: Proxy flag -> a -> IO ()

instance Show a => Print' HTrue a where
  print' _ x = putStrLn (show x)

instance Print' HFalse a where
  print' _ _ = putStrLn "No show method"

Solution

  • The initial error was caused by

    instance Show a => Print' HTrue a where
      print' _ x = putStrLn (show x)
    
    instance Print' flag a where
      print' _ _ = putStrLn "No show method"
    

    These instances overlap when flag ~ HTrue. The solution to this is to replace the second instance with

    instance Print' HFalse a where ...
    

    Your next problem was with

    instance (ShowPred a ~ flag, Print' flag a) => Print a where
      print = print' (undefined :: flag)
    

    By default, the flag variable in the instance context is not in scope in the instance body, so undefined :: flag has an ambiguous type. The solution is to enable ScopedTypeVariables.

    My final note is that instead of making print' take a value of type flag and then passing it undefined, you should probably use

    print' :: proxy flag -> a -> IO ()
    

    and then import Data.Proxy to define

    print = print' (Proxy :: Proxy flag)
    

    The modern proxy-passing approach seems to be quite new, although less-satisfactory versions have been around for some time. See the answer to this question for some historical context.