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?