You may recall the definition for an exponential functor from my previous entry, which can also be viewed, I suppose, as functors in the
category of right-invertible functions in Haskell.
class ExpFunctor f where
xmap :: (a -> b) -> (b -> a) -> f a -> f b
Clarifying the above, an instance of ExpFunctor should satisfy the slightly generalized version of the Functor laws from Control.Monad:
xmap id id = id
xmap f g . xmap f' g' = xmap (f . f') (g' . g)
Since we like to apply xmap to a pair of functions such that f . g = id, as in the Fegaras/Sheard case, we get:
xmap f g . xmap g f
= xmap (f . g) (f . g) -- by second xmap law
= xmap id id -- by f . g = id
= id -- by first xmap law
In any event, what I thought what I'd do today is note that Wouter Swierstra's Data Types a la Carte approach works over exponential functors.
Swierstra's main definition looks something like:
data (f :+: g) e = Inl (f e) | Inr (g e)
instance (Functor f, Functor g) => Functor (f :+: g) where
fmap f (Inl e) = Inl (fmap f e)
fmap f (Inr e) = Inr (fmap f e)
This permits the obvious analogue:
instance (ExpFunctor f, ExpFunctor g) => ExpFunctor (f :+: g) where
xmap f g (Inl e) = Inl (xmap f g e)
xmap f g (Inr e) = Inr (xmap f g e)
With this we can quickly encode the untyped lambda calculus:
newtype Lam a = Lam (a -> a)
instance ExpFunctor Lam where
xmap f g (Lam k) = Lam (f . k . g)
data App a = App a a
instance Functor App where
fmap f (App a b) = App (f a) (f b)
instance ExpFunctor App where
xmap = const . fmap
(Even better we could define a generic Bind and Binary functors, and newtype to get Lam and App)
and we can encode the recursion using any of the ways we previously established to tie the knot (Nu f, ForAll (Rec f), ForAll (Elim f)).
The rest of the a la carte stuff remains unchanged.
We then get a definition of catamorphism 'for free', due to the definitions from the other day.
From there we can define a fairly generic pretty printing framework.
class ShowAlgebra f where
showAlgebra :: f ([String] -> String) -> [String] -> String
instance (ShowAlgebra f, ShowAlgebra g) => ShowAlgebra (f :+: g) where
showAlgebra (Inl e) = showAlgebra e
showAlgebra (Inr e) = showAlgebra e
instance (Cata f (ForAll t), ShowAlgebra f) => Show (ForAll t) where
show x = cata showAlgebra (runForAll x) vars
And then we can derive particular instances for the untyped lambda calculus we slapped together above:
instance ShowAlgebra Lam where
showAlgebra (Lam k) (v:vars) =
"(\\\\" ++ v ++ ". " ++ k (const v) vars ++ ")"
instance ShowAlgebra App where
showAlgebra (App a b) vars =
"(" ++ a vars ++ " " ++ b vars ++ ")"
Then using the combinators from the other day:
type Term a = Elim (Lam :+: App) a
type Expr = ForAll Term
app_id_id :: Term a
app_id_id = app (lam id) (lam id)
-- for suitable definitions of app and lam a la the a la Carte paper.
main = putStrLn . show $ safe app_id_id
Other algebra structures can be derived similarly.