Erroneous behavior of m-lift in clojure.algo.monads?! No, it’s only me…again!

I’m back to algo.monads.

I can’t remember how much time I’ve already devoted to the library and the topic of monads in general, but I’m sure it took me already a couple of weeks and still whenever I’m back to algo.monads I find something interesting.

Today I thought I found a bug in m-lift macro and even reported an issue to Konrad Hinsen (the author of the library), but when I read the article A Monad Tutorial For Clojure Programmers (Part 1) I knew I was wrong.

I just badly needed the macroexpand-1 function. I should’ve remembered to give it a shot before I ran the alarm!

The m-lift macro “Converts a function f of n arguments into a function of n monadic arguments returning a monadic value.” I thought it was so easy to comprehend, but was so much mistaken.

Have a look at the REPL session below.

jacek:~/sandbox
$ lein2 version
Leiningen 2.0.0-SNAPSHOT on Java 1.7.0-u10-b09 OpenJDK 64-Bit Server VM
jacek:~/sandbox
$ lein2 repl
nREPL server started on port 54046
REPL-y 0.1.2
Clojure 1.4.0
    Exit: Control+D or (exit) or (quit)
Commands: (user/help)
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
          (user/sourcery function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
Examples from clojuredocs.org: [clojuredocs or cdoc]
          (user/clojuredocs name-here)
          (user/clojuredocs "ns-here" "name-here")
user=> (use '[cemerick.pomegranate :only (add-dependencies)])
nil
user=> (add-dependencies :coordinates '[[org.clojure/algo.monads "0.1.3-SNAPSHOT"]])
{[org.clojure/clojure "1.3.0"] nil, [org.clojure/tools.macro "0.1.0"] nil, [org.clojure/algo.monads "0.1.3-SNAPSHOT"] #{[org.clojure/tools.macro "0.1.0"] [org.clojure/clojure "1.3.0"]}}
user=> (require '(clojure.algo [monads :as m]))
nil
user=> (doc m/m-lift)
-------------------------
clojure.algo.monads/m-lift
([n f])
Macro
  Converts a function f of n arguments into a function of n
  monadic arguments returning a monadic value.
nil
user=> (m/m-lift 1 (fn [v] v))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: m-bind in this context, compiling:(NO_SOURCE_PATH:1)

See the RuntimeException? That’s where I thought I found a bug. I thought the m-bind should already been defined, but it was not. And it is on purpose – the m-bind is a part of a monad and it’s up to the monad to register it.

user=> (macroexpand-1 '(m/m-lift 1 (fn [v] v)))
(clojure.core/fn [mv_5837] (m-bind mv_5837 (fn [x_5838] (m-result ((fn [v] v) x_5838)))))

It was when I realized I needed a monad. There are a few available, with identity-m being the simplest one.

user=> (source m/identity-m)
(defmonad identity-m
   "Monad describing plain computations. This monad does in fact nothing
    at all. It is useful for testing, for combination with monad
    transformers, and for code that is parameterized with a monad."

  [m-result identity
   m-bind   (fn m-result-id [mv f]
              (f mv))
  ])
nil
user=> (m/with-monad m/identity-m (m/m-lift 1 (fn [v] v)))
#<user$eval5844$fn__5847 user$eval5844$fn__5847@3330fadd>
user=> (macroexpand-1 '(m/with-monad m/identity-m (m/m-lift 1 (fn [v] v))))
(clojure.core/let [name__5505__auto__ m/identity-m m-bind (:m-bind name__5505__auto__) m-result (:m-result name__5505__auto__) m-zero (:m-zero name__5505__auto__) m-plus (:m-plus name__5505__auto__)] (clojure.tools.macro/with-symbol-macros (m/m-lift 1 (fn [v] v))))

As you can see m-bind and m-result (the two mandatory monad’s functions) are already bound and available. That’s how monads are implemented in algo.monads and with no monad there’s no m-bind and m-result. Simple, isn’t it?

I think I’ll now remember. Sorry for bothering.

Be Sociable, Share!
This entry was posted in Languages.

Leave a Reply

%d bloggers like this: