Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: Using the correct instance for a type wrapped in another type

Tags:

types

haskell

Say we have the following code, where a couple of types end up being place inside two other types, the outer-most of which is a GADT:

{-# LANGUAGE FlexibleInstances,
             GADTSyntax,
             GADTs,
             OverlappingInstances,
             StandaloneDeriving #-}

data SomeType1 = SomeType1 deriving Show
data SomeType2 = SomeType2 deriving Show

class SomeClass d where

instance SomeClass SomeType1 where
instance SomeClass SomeType2 where

data WrapperType t where
  WrapperType :: (SomeClass t, Show t) => t -> (WrapperType t)

instance Show (WrapperType SomeType1) where
  show (WrapperType d) = "correct"

instance Show (WrapperType t) where
  show (WrapperType d) = "incorrect"

data ListWrap where
  ListWrap :: [(WrapperType d)] -> ListWrap

deriving instance Show ListWrap

Now, writing [WrapperType SomeType1] gives me what I want:

*MyModule> [WrapperType SomeType1]
[correct]

But as soon as I put it inside ListWrap I get the wrong Show instance picked to display the contents:

*MyModule> ListWrap [WrapperType SomeType1]
ListWrap [incorrect]

There must be something about type classes and/or GADTs that I'm failing to understand -- what could it be?

like image 932
Edward Lilley Avatar asked Jul 30 '13 22:07

Edward Lilley


1 Answers

If you look at the derived code (with -ddump-deriv) then you can see that the derived instance for ListWrap is pretty normal looking

instance Show ListWrap where
  showsPrec a (ListWrap b) =
    showParen (a >= 11) $ showString "ListWrap " . showsPrec 11 b

it's just passing the inner show down to showsPrec again. This suggests that the problem is that GHC is erasing the type variable d when you wrap it with ListWrap. Indeed, you could also write ListWrap as

data ListWrap where
  ListWrap :: forall d. [(WrapperType d)] -> ListWrap

which emphasizes that this is existentially typed.

We can see the error more directly by deleting the instance Show (WrapperType t) and observing GHC's type error

/Users/tel/tmp/foo.hs:33:52: Warning:
    No instance for (Show (WrapperType d))
      arising from a use of `showsPrec'
    Possible fix:
      add an instance declaration for (Show (WrapperType d))
    In the second argument of `(.)', namely `showsPrec 11 b'
    In the second argument of `($)', namely
      `showString "ListWrap " . showsPrec 11 b'
    In the expression:
      showParen (a >= 11) $ showString "ListWrap " . showsPrec 11 b
Ok, modules loaded: Main.

In other words, it has lost the details about the d type and thus cannot unify the specific instance Show (WrapperType SomeType1).


Now you would think that this would imply that keeping that type information around would make the type error go away.

data ListWrap d where
  ListWrap :: [(WrapperType d)] -> ListWrap d

> show $ ListWrap [WrapperType SomeType1]
"ListWrap [incorrect]"

But it seems like the instance search is going awry as well. The only way I could find to make it work was to turn on UndecidableInstances and provide a suggestion for instance derivation.

deriving instance Show (WrapperType d) => Show (ListWrap d)

after which the example works

> show $ ListWrap [WrapperType SomeType1]
"ListWrap [correct]"
like image 90
J. Abrahamson Avatar answered Sep 22 '22 16:09

J. Abrahamson