There has been some outstanding work with Monads in Clojure by Konrad Hinsen, Jim Duey and Leonardo Borges.
My question is - is it possible to do the Free Monad in Clojure?
This is an example in Haskell from an article on Scala:
data Free f r = Free (f (Free f r)) | Pure r
This is the corresponding Scala example
sealed abstract class Free[S[+_], +A](implicit S: Functor[S]) {
final def map[B](f: A => B): Free[S, B] =
flatMap(a => Return(f(a)))
final def flatMap[B](f: A => Free[S, B]): Free[S, B] = this match {
case Gosub(a, g) => Gosub(a, (x: Any) => Gosub(g(x), f))
case a => Gosub(a, f)
}
...
}
It can definitely be done, but a key thing is that the idiomatic Haskell implementation of free monads is based on exploiting the type system to provide a certain kind of modularity and well-formedness guarantees. The idioms used may well not be idiomatic in Clojure, and it might be best to come up with a different sort of implementation.
Just look at the full definition of the Free
monad in Haskell:
data Free f r = Free (f (Free f r)) | Pure r
instance Functor f => Monad (Free f) where
-- Construct a "pure value" leaf node
return = Pure
-- Replace every "pure value" leaf node with the tree obtained by
-- applying `f` to its value. (Remember that Haskell is lazy, so this
-- tree is only created as it is consumed, and only those branches that are
-- in fact consumed.)
Pure a >>= f = f a
Free fa >>= f = Free (fmap (>>=f) fa)
This is using the following Haskell features:
Functor
and Monad
Free
is a recursive typef
in Free f r
is actually used as a type constructor—the definition is abstracting over a container type while constraining the element type.Then it's also using a type-level fixed point construction, a technique that doesn't exist in Clojure. The simplest version of this is this type:
newtype Fix f = Fix (f (Fix f))
If you've ever read about the Y combinator, you can think of this as an analogue of it, but at the level of types, not of values.
But putting aside all that, let's focus on the essential structure here: a free monad is basically a kind of lazily generated, possibly infinite tree structure. The Functor
used in a free monad does two things:
(Don't fall into the misconception of looking at the free monadic tree as an "abstract syntax tree"; the nodes of the tree don't represent expressions or anything like that.)
The core generic free monad code then provides two things:
Pure
node type that is guaranteed to exist in every free monad. These nodes just contain a value, and no children.Pure
leaves with the result of applying their value to a function.After you have this, by supplying a suitable functor you can use the generic free monad code to construct lazy trees according to the structure that your functor provides. These trees are just inert values; you consume them by writing interpreter functions that navigate the tree according to some strategy in order to produce the results you need. But actually, you'd want your free monad library to have some suitable generic utility functions to help you write interpreters more easily. For example:
-- | Generic recursive fold over a free monadic tree.
iter :: Functor f => (f a -> a) -> Free f a -> a
iter f (Pure a) = a
iter f (Free fa) = f (fmap (iter f) fa)
-- | If you can map any node to a corresponding action in another monad, you can map
-- the whole free monadic tree to a monadic action as well...
interpret :: (Functor f, Monad m) => (forall x. f x -> m x) -> Free f a -> m a
So the obvious question if one wants to write this in Clojure (or any other Lisp) is: why would one do this instead of just writing an s-expression DSL interpreter?
Well, one thing free monads give you is a kind of normal-form representation of monadic programs. For example, think of the following similar snippets of Clojure and Haskell code:
;;; Clojure; these two expressions always produce the same result and
;;; have the same effects
(do a (do b c))
(do (do a b) c)
-- Haskell counterparts
(a >> b) >> c
a >> (b >> c)
The s-expression syntax in the Clojure forms allows you to write two different expressions that nonetheless are required to always behave the same. In Haskell, on the other hand, the Free
monad definition guarantees that the two expressions above evaluate to exactly the same value—no program can distinguish them! So you could write a buggy s-exp interpreter or macro that treated those two Clojure forms differently, but not so for the free monadic trees.
Another set of potential advantages are that free monads provide some standard techniques for implementing language features like backtracking (use some kind of list as your functor, representing alternatives in the order in which they are to be considered) and pausing/resuming the interpretation of a program (free monads are closely related to continuation-passing style).
But for maximum idiomaticity in a Lisp, you probably want to combine both techniques: use s-exprs as a front end that you translate, using some generic library, to a free monad representation according to client-supplied definitions of the special operations of the DSL. Provide also generic functions to simplify the client's task of writing interpreters for their free monadic language.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With