[Haskell-cafe] Type classes to 'reflect' constructor structure

Jules Bean jules at jellybean.co.uk
Thu Apr 5 09:22:46 EDT 2007


In the thread 'automatic derivation', Joel Reymont is looking for 
metaprogramming functionality with which he wants to automatically 
derive a parser and a pretty printer for his ADT (which is an AST for a 
minilanguage).

I replied showing that a significant amount of the boilerplate could be 
removed anyway just using haskell's built in ability to process parsers 
as 'data'. I could completely automate the nullary constructions, but I 
needed type information for n-ary ones.

A bit of poking around with typeclasses showed a proof-of-concept for 
getting the type-checker to extract that information for us:

{-# OPTIONS -fglasgow-exts #-}
import Data.Typeable


-- Stage 1 is just counting the arguments

class CountArgs s where numArgs :: s -> Integer

data TestType = Nullary | Unary Int | Binary Int String
              | OtherBinary String Int

instance CountArgs TestType where numArgs x = 0
instance CountArgs (a->TestType) where numArgs x = 1
instance CountArgs (a->b->TestType) where numArgs x = 2

-- *Main> numArgs Nullary
-- 0
-- *Main> numArgs Unary
-- 1
-- *Main> numArgs Binary
-- 2

-- Stage 2 actually lists the types of the arguments
-- I'll use a seperate ADT to make the types concrete

data ArgTypes = JInt | JStr deriving (Show)

class ConcreteType t where makeAT :: t -> ArgTypes

instance ConcreteType Int where makeAT _ = JInt
instance ConcreteType String where makeAT _ = JStr

class DescribeArgs s where descArgs :: s -> [ArgTypes]

instance DescribeArgs TestType  where descArgs _ = []
instance ConcreteType a => DescribeArgs (a->TestType)
 where descArgs _ = [makeAT (undefined::a)]
instance (ConcreteType a, ConcreteType b) =>
    DescribeArgs (a->b->TestType)
 where descArgs _ = [makeAT (undefined::a), makeAT (undefined::b)]

-- *Main> descArgs Nullary
-- []
-- *Main> descArgs Unary
-- [JInt]
-- *Main> descArgs Binary
-- [JInt,JStr]
-- *Main> descArgs OtherBinary
-- [JStr,JInt]

-- Stage 3 is just the Data.Typeable version of the stage 2

class DescribeArgs2 s where descArgs2 :: s -> [TypeRep]

instance DescribeArgs2 TestType  where descArgs2 _ = []
instance Typeable a => DescribeArgs2 (a->TestType)
 where descArgs2 _ = [typeOf (undefined::a)]
instance (Typeable a, Typeable b) =>
    DescribeArgs2 (a->b->TestType)
 where descArgs2 _ = [typeOf (undefined::a), typeOf (undefined::b)]

-- *Main> descArgs2 Nullary
-- []
-- *Main> descArgs2 Unary
-- [Int]
-- *Main> descArgs2 Binary
-- [Int,[Char]]
-- *Main> descArgs2 OtherBinary
-- [[Char],Int]


There are still some things this approach fails on: it can't give you a 
complete list of all constructors of TestType, for example. (Such a list 
would necessarily an existential type, like [exists x . DescribeArgs x 
-> x]).

I'm sure my thoughts aren't original. Have other people taken this 
further into interesting directions? Where is the line beyond which you 
need 'true' metaprogramming?

Jules



More information about the Haskell-Cafe mailing list