Search code examples
haskelltypeclass

Can't write a Show instance for this type


Consider the following type declaration:

data Foobar x = Foo (x (Foobar x))

Now write a Show instance for that. I dare you!

I casually wrote deriving Show, expecting GHC to do it's thing. But it complained that it couldn't do it. "Poor simpleton", I thought, and proceeded to define it myself. But... um... oh, actually that's quite interesting... Clearly Foobar x is only showable when x y is showable for showable y... or... something like that... aaargh!

Now I'm sure I cannot possibly be the first person to define a type such as this. And I can't be the first person to want a Show instance for it. So somebody must have figured out how to do this.


Solution

  • If you just specify the obvious constraint – and add some necessary language pragmas – everything works just fine. For Foobar to be showable, the field of Foo must be showable, so we require a Show (x (Foobar x)) constraint. And if we ask for that, everything just works out:

    {-# LANGUAGE FlexibleContexts, UndecidableInstances, StandaloneDeriving #-}
    
    data Foobar x = Foo (x (Foobar x))
    deriving instance Show (x (Foobar x)) => Show (Foobar x)
    

    Then:

    λ> print $ Foo [Foo [], Foo [Foo []]]
    Foo [Foo [],Foo [Foo []]]
    λ> data SL a = S String | L [a] deriving Show
    λ> print $ Foo (L [Foo (S "a"), Foo (L [Foo (S "b"), Foo (L []), Foo (S "c")])])
    Foo (L [Foo (S "a"),Foo (L [Foo (S "b"),Foo (L []),Foo (S "c")])])
    

    I'm a little bit surprised this works, but not overly so :-)


    Incidentally, this is exactly what GHC asks of you if you try to derive Show for Foobar:

    λ> data Foobar x = Foo (x (Foobar x)) deriving Show
    
    <interactive>:2:45:
        No instance for (Show (x (Foobar x)))
          arising from the first field of ‘Foo’ (type ‘x (Foobar x)’)
        Possible fix:
          use a standalone 'deriving instance' declaration,
            so you can specify the instance context yourself
        When deriving the instance for (Show (Foobar x))
    

    All we had to do was use StandaloneDeriving and specify exactly that constraint!


    Also, to be clear, the StandaloneDeriving part is optional – it's not doing any magic, and the instance isn't too hard to write by hand:

    instance Show (x (Foobar x)) => Show (Foobar x) where
      showsPrec p (Foo x) = showParen (p > app_prec) $
                              showString "Foo " . showsPrec (app_prec + 1) x
        where app_prec = 10 :: Int