Is it possible to do the Free Monad in Clojure?

2019-03-28 00:48发布

问题:

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)
  }
  ...
}

回答1:

Yes, following Luis Casillas answer, here is an implementation in clojure of the Free monad in clojure.

    (use 'clojure.algo.monads)

    ;; data Free f r = Free (f (Free f r)) | Pure r
    (defn pure [v] {:t :pure :v v})
    (defn impure [v] {:t :impure :v v })

    (defn free-monad
    [fmap]
    (letfn [
            (fm-result [v] (pure v))         
            (fm-bind [mv mf]                 
                (if (= :pure (:t mv))
                    (mf (:v mv))             ;; Pure a >>= f = f a
                    (impure                  ;; Free fa >>= f = Free (fmap (>>=f) fa) 
                     ((fmap (fn [lmv] (fm-bind lmv mf))) (:v mv)))))          
            ]
        {
        :m-result fm-result
        :m-bind fm-bind
        :m-zero ::undefined 
        :m-plus ::undefined
        }
        ) 
    )

And an example from Why free monads matter:

Definition of toy language.

    ;; Toy language
    ;; 
    (defn output [c n] [{:t :output :v c}, n])
    (defn bell [n] [{:t :bell}, n]) 
    (defn done [] [{:t :done}, nil]) 
    (defn toy-fmap [f]
    (fn [[e c]]
    (if (= :done (:t e))
        [e c]
        [e (f c)] 
        ))  
    )

Definition of the monad for the toy language + helper functions

    ;;
    (def tt-monad 
    (free-monad toy-fmap))


    (defn liftF [toy]
    (impure ((toy-fmap (fn [c] (pure c))) toy))
    )

    (defn m-output [x] (liftF (output x nil)))
    (defn m-bell [] (liftF (bell nil)))
    (defn m-done [] (liftF (done)))
    (defn f-m-done [_] (m-done))

And checking some rules :

    ;; return "a" >>= output  is Free(Output "a", ())
    ;; {:t :impure, :v [{:t :output, :v \a} {:t :pure, :v nil}]}  
    (with-monad tt-monad
    (m-bind (m-result \a) m-output)
    )


    ;; output "a" >>= return  is Free(Output "a", ())
    ;; {:t :impure, :v [{:t :output, :v \a} {:t :pure, :v nil}]}
    (with-monad tt-monad
    (m-bind (m-output \a) m-result)
    )

    ;; ((output 'A' >> done) >> output 'C')
    (with-monad tt-monad
    (m-bind (m-bind (m-output \a) f-m-done) (fn [_] (m-output \c))))

    ;;(output 'A' >> (done >> output 'C')) is output 'A' Done
    (with-monad tt-monad
    (m-bind (m-output \a) (fn [x] (m-bind (m-done) (fn [_] (m-output \c))))))

This could be improved quite a lot in terms of readability of the data structure. Comments and improvements most welcome.



回答2:

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:

  1. Type classes: Functor and Monad
  2. Recursive data types: Free is a recursive type
  3. Higher-kinded polymorphism: note that the type variable f 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:

  1. Define what types of nodes exist in the tree, and what data is carried at each node
  2. Allow the generic free monad code to map a function over the children of any node.

(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:

  1. A Pure node type that is guaranteed to exist in every free monad. These nodes just contain a value, and no children.
  2. A bind method that lazily replaces all of the 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.



回答3:

These have been two awesome answers, that directly answer your question and should be deferred to.

I'm still learning myself, and would like to expand the question by mentioning that Free Monads are often accompanied with interpreters, and the following is a simple one that I built for the free monad mentioned by @cotarmanach's.

This could should plug and play with his, to provide a printing interpreter to the data structure built in the following domonad expression:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INTERPRETER
;;

(def pavlov (domonad tt-monad [a (m-output "hi there pavlov")
                               b (m-bell)
                               c (m-output "howdya like me now?")
                               d (m-bell)
                               e (m-bell)
                               f (m-bell)
                               g (m-done)]
                     g))

(defn bell-interpreter [program]
  (let [{ft           :t ; free type: impure or pure?
         [{t :t
           v :v} next] :v} program]
    (if (= :pure ft)
      "error, shouldn't have a `pure` type"
      (case t
        :output (do (prn v) (bell-interpreter next))
        :bell (do (prn "rinnnng") (bell-interpreter next))
        :done "done!"
        :otherwise "uh oh, that's not a type I recognize"))))
  1. For each node, check if it's Free/Impure or Pure.

  2. If it's Impure, then it's one of our "defined" types, so we can interpret it. Create a case for each of our "types", and make sure to call the next member of this recursive data type.