This might be the least interesting article title on the Internet. However, it is something I have been looking into as I work on my language, psilo.
So I have a few overlapping concerns:
psilo, like Haskell, doesn't semantically support imperative programming (ie, recipe-style "statement; statement; ..." programming). However, I'm not a sadist: I want the programmer to be able to write more intuitive programs if she can earn it.
I'm a fan of the free monad pattern in Haskell but I don't want to support typeclasses. Thus I am interested in implementing free monad infrastructure using nothing but algebraic data types.
Since delimited continuations are tantamount to monads, I want the user to be able to write what amounts to an abstract syntax tree type and a few functions in continuation passing style and in return receive a monad,
donotation, and composable imperative programming constructs.
This post is a simple exploration of implementing the Functor typeclass as an algebraic data type, and then deriving a free monad from it.
1 Functors and Free Monads
{-# LANGUAGE RankNTypes #-}
data F f = F {
_map :: forall a b. (a -> b) -> f a -> f b
}An instance of F should contain a function called _map constrained by whichever base type is being functor-ized.
data Mu f a
= Term { retract :: a }
| Cont (f (Mu f a))Mu is the free monad. I like calling it Mu for two reasons: because calling it "free" is confusing and not informative; and because Mu behaves like the type-level analog to the Y-combinator, called Mu.
unit :: a -> Mu f a
unit = Term
yield :: a -> Mu f a
yield = TermThese two are different names for the same thing, mostly for aesthetic reasons. You'll see.
bind :: F f -> Mu f a -> (a -> Mu f b) -> Mu f b
bind i arg fn = case arg of
Term t -> fn t
Cont k -> Cont (map (bind' fn) k)
where map = _map i
bind' = flip (bind i)This is the cool part. bind is defined for any type which is wrapped by Mu, and for a type to be wrapped by Mu it must instantiate our F type. Thus we can ask for an "instance" argument, called i here.
sequence' i x = case x of
[] -> unit []
m:ms -> m >>= \x ->
sequence' i ms >>= \xs ->
unit (x:xs)
where
(>>=) = bind iFor kicks I have re-written the sequence function available in Haskell. psilo will essentially use this or an equivalent function to implement do notation under the covers.
2 An example: re-creating the Maybe monad.
Apple's Swift programming language seems to have ignited more widespread interest in optional types. So, just to show there is nothing up my sleeves, I am re-creating the Maybe monad with the name Optional.
data Optional s
= Nil
| Some s
deriving (Show)Now for the fun part. I will create an F instance for Optional along with some convenience functions to use in monadic computations. While I don't do it here, this could be derived automatically.
fOptional :: F Optional
fOptional = F {
_map = \f x -> case x of
Nil -> Nil
Some s -> Some $ f s
}
nil = Cont Nil
some = TermIn ~12 lines of real code, I have created a Maybe clone and proven it is a functor. As a result all the remaining code necessary to compose a monad has been derived automatically.
Since this was a free monad, the only remaining code is that to "run" the monadic computation built up using unit and bind.
runOptional :: Mu Optional a -> Optional a
runOptional (Term t) = Some t
runOptional (Cont k) = case k of
Nil -> Nil
Some v -> runOptional v3 Tests!
Without further ado, here is some example code written in our Optional monad.
testOptional1 = some 5 >>= \a ->
some 6 >>= \b ->
yield $ a * b
where (>>=) = bind fOptional
testOptional2 = some 5 >>= \a ->
nil >>= \b ->
some 6 >>= \c ->
yield $ a * c
where (>>=) = bind fOptionalTry it out for yourself to see the results.