I was recently introduced to functional dependencies and type families. For a class project I wrote (completed) an interpreter for a subset of C in Java and Haskell. The Haskell implementation of an evaluation function for terms required building "function tables" with explicit pattern matching and unwrapping of value constructors representing literals. An unhappy situation (but much prettier than the Java).
After searching for a while, I came across the "collections" example, wondering if I could apply this to my abstract syntax to produce generic "inject to" and "project from" functions for literals. I came up with two initial test attempts:
(Using functional dependencies: the injection and projection functions work without explicit type annotations, as does injection into Lit with lit. However, the projection function from Lit will not type, giving the error "couldn't match expected type l
against inferred type l'
".)
class Prim l a | l -> a, a -> l where
inj :: a -> l
proj :: l -> a
instance Prim LB Bool where inj = LB; proj = lb
instance Prim LI Int where inj = LI; proj = li
data LB = LB {lb :: Bool}
data LI = LI {li :: Int}
data E where Lit :: Prim l a => l -> E
lit :: Prim l a => l -> E
lit = Lit
unlit :: Prim l a => E -> l
unlit (Lit a) = a
(Using type families. The problem here is that I can't get Haskell to infer from an argument the correct return type without explicit annotation, and I can't write the generic functions lit :: Val l -> E
and unlit :: E -> Val l
.)
class Show l => Prim l where
type Val l :: *
inj :: Val l -> l
proj :: l -> Val l
data LB a = LB {lb :: Bool}
data LI a = LI {li :: Int }
instance Prim (LB a) where type Val (LB a) = Bool; inj = LB; proj = lb
instance Prim (LI a) where type Val (LI a) = Int; inj = LI; proj = li;
data E where
Lit :: Prim l => l -> E
Bin :: Op -> E -> E -> E
I don't understanding type families well, and have a flimsy grasp on functional dependencies. But I'd like to know two things: (a) if the functions I want can be typed and implemented; (b) If I am misunderstanding something fundamental here. It almost works, but I've been fighting with the type checker for a while now.
EDIT This is a simple model of what I want, since it was unclear. The class Bin
implements the functionality I want, basically. But I can't collect the various "wrappable and unwrappable" values together to make an AST out of this.
class L t l => Bin t l where
bAp :: (t -> t -> t) -> l -> l -> l
bAp f l r = inj (proj l `f` proj r)
class Show l => L t l | t -> l, l -> t where
inj :: t -> l
proj :: l -> t
typ :: l -> T
instance Bin Int LI
instance Bin Bool LB
instance L Int LI where inj = LI; proj = li; typ = const TI
instance L Bool LB where inj = LB; proj = lb; typ = const TB
data LI = LI {li :: Int} deriving (Eq, Show)
data LB = LB {lb :: Bool} deriving (Eq, Show)
data T = TI | TB | TC | TF | TU deriving (Eq, Show)