2

I am having trouble defining a Show instances for the heterogeneous list defined below:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE FlexibleInstances #-}

import Data.Kind

data HList xs where
     HNil :: HList TNil
     (::^) :: a -> HList as -> HList (a :^ as)

data TypeList = TNil | (:^) Type TypeList

instance Show (HList TNil) where
     show HNil = "[]"

I would like to give HList xs a show instance, if all types in the Typelist xs have a Show Instance. I guess one should be able to write something like

instance (Show a, _) => Show (HList a :^ as) where
     show (x ::^ xs) = show x ++ show xs

but I am unsure what to fill in the hole _.

PS: If you try this in ghci, don't forget to add the language extensions with

:set -XTypeInType -XTypeOperators
mna
  • 263
  • 2
  • 8

2 Answers2

6
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}

import Data.Kind

infixr 5 ::^

data HList xs where
     HNil :: HList TNil
     (::^) :: a -> HList as -> HList (a :^ as)

data TypeList = TNil | (:^) Type TypeList

instance Show (HList TNil) where
     show HNil = "HNil"

instance (Show a, Show (HList as)) => Show (HList (a:^as)) where
     showsPrec p (x::^xs) = showParen (p>5)
       $ showsPrec 6 x . ("::^"++) . showsPrec 5 xs

main :: IO ()
main = print ((2 :: Int) ::^ "bla" ::^ HNil)
2::^"bla"::^HNil
mna
  • 263
  • 2
  • 8
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
4

Using type families, you can express the constraint "All types have a show instance" pretty directly:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE UndecidableInstances #-}

import Data.Kind

infixr 5 ::^
data HList xs where
     HNil :: HList TNil
     (::^) :: a -> HList as -> HList (a :^ as)

data TypeList = TNil | (:^) Type TypeList

type family All (c :: Type -> Constraint) (xs :: TypeList) :: Constraint where
        All c TNil = ()
        All c (x :^ xs) = (c x, All c xs)

instance All Show xs => Show (HList xs) where
    show HNil = "[]"
    show (x ::^ xs) = '[' : show x ++ go xs
      where
        go :: All Show ys => HList ys -> String
        go HNil = "]"
        go (y ::^ ys) = ',' : show y ++ go ys
oisdk
  • 9,763
  • 4
  • 18
  • 36
  • I like this more than the other answer because `All` is reusable. Also, it avoids splitting the instance in two. – HTNW May 13 '18 at 22:01
  • But it does use UndecidableInstances. – mna May 13 '18 at 22:16
  • 1
    Yes, but it's a safe use of UndecidableInstances, since we know that `All` will terminate given a finite list. – oisdk May 14 '18 at 00:38
  • I actually tried to construct an infinite list in TypeList. It did not work, which is surprising since the value-level analog allows this. I guess the type level language is more restrictive, so that TypeList is always a finite list. – mna May 14 '18 at 02:58
  • I do love the flexibility of your All solution! It is useful for writing other instances. I am curious if there is a trick to get rid of the UndecidableInstance. – mna May 14 '18 at 03:02
  • @mna, it is possible to express infinite types using type families. They're not well behaved. `type family Foo :: [Bool] where Foo = 'True ': Foo`. – dfeuer May 14 '18 at 05:59