The following code shows a tagless final lambda typeclass and an evaluation instance:
class Lambda (repr :: * -> *) where
int :: Int -> repr Int
add :: repr Int -> repr Int -> repr Int
lambda :: forall a b. (repr a -> repr b) -> repr (a -> b)
apply :: forall a b. repr (a -> b) -> repr a -> repr b
-- eval
instance Lambda Identity where
int = Identity
add x y = Identity $ runIdentity x + runIdentity y
lambda f = Identity $ \x -> runIdentity (f (Identity x))
apply (Identity f) (Identity x) = Identity $ f x
But there are lots of boilerplate of plugging Identity
and runIdentity
. How can I avoid them?
I tried to use the type family as following:
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeFamilies #-}
class Lambda f where
type Repr f a
int :: Int -> Repr f Int
add :: Repr f Int -> Repr f Int -> Repr f Int
lambda :: forall a b. (Repr f a -> Repr f b) -> Repr f (a -> b)
apply :: forall a b. Repr f (a -> b) -> Repr f a -> Repr f b
data Eval
instance Lambda Eval where
type Repr Eval a = a
int = id
add = (+)
lambda = id
apply f = f
I have two questions:
- Why is the following declaration incurs an error?
exp1 :: Lambda f => Repr f Int
exp1 = int 1
with error:
app/Main.hs:63:8: error:
• Couldn't match expected type: Repr f Int
with actual type: Repr f0 Int
NB: ‘Repr’ is a non-injective type family
The type variable ‘f0’ is ambiguous
• In the expression: int 1
In an equation for ‘exp1’: exp1 = int 1
• Relevant bindings include
exp1 :: Repr f Int (bound at app/Main.hs:63:1)
|
63 | exp1 = int 1
| ^^^^^
- Is it a good practice to write the tagless final with the type family?