Reputation: 5410
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)
Upvotes: 2
Views: 618
Reputation: 13223
If you still want to stick with idea of polymorphic E
. You can use polymorphic functions:
withUnlit :: E -> (forall l . Prim l => l -> b) -> b
withUnlit (Lit a) f = f a
But the only thing you can do (with traits you've given to Prim l
) is:
showE :: E -> String
showE e = withUnlit e show
And inj
and proj
. But you have no way to work with Val l
except of using some Data.Dynamic
(if that is what I think).
Upvotes: 1
Reputation: 7751
The way you have defined the Lit
constructor will prevent you from projecting out the value it contains, regardless of how you define the projection function.
Let's look at the constructor's type:
Lit :: Prim l => l -> E
The type variable l
appears in the parameters, but not the return type. That means that when you construct a Lit, you put in a value of some type that's a member of Prim and then permanently forget what its type was.
I'm not sure how you want to eliminate pattern matching and unwrapping of value constructors. You basically have two choices for how to do projections:
There are reasons to go with compile-time proofs, but it doesn't look like you have any of those reasons.
Upvotes: 4