Clojure's Thread for Monads
gga
#2013-12-05
Over the last few months, I’ve really been enjoying writing
Clojure. In an effort to learn the language better, I’ve kept leaning
on my code to make it clearer and more concise. Eventually, pulling in
algo.monads
to model my error handling as a monad.
I was surprised to discover that there is no ->
macro in the monad
library. So here’s an implementation:
(defn- bind-monadic-expr-into-form [insert form]
(list 'm-bind insert (if (seq? form)
`(fn [bound#] (~(first form) bound# ~@(rest form)))
`(fn [bound#] (~form bound#)))))
(defmacro m->
([m x]
`(monad/with-monad ~m ~x))
([m x form]
`(monad/with-monad ~m
~(bind-monadic-expr-into-form x form)))
([m x form & more]
`(m-> ~m (m-> ~m ~x ~form) ~@more)))
You can use the m->
macro to create thread macros around your own
monads. I use one around the failure-m
monad from
Andrew Brehaut. This is sort of like a Maybe
monad, with some
more information.
(defprotocol Failed
(has-failed? [failure]))
(extend-protocol Failed
Object
(has-failed? [failure] false)
Failure
(has-failed? [failure] true)
Exception
(has-failed? [failure] true))
(monad/defmonad failure-m
[m-result identity
m-bind (fn [m f]
(if (has-failed? m)
m
(f m)))])
(defmacro fail->
([x] `(m-> ~failure-m ~x))
([x form] `(m-> failure-m ~x ~form))
([x form & more] `(fail-> (fail-> ~x ~form) ~@more)))
(defmacro dofailure [bindings expr]
`(monad/domonad failure-m ~bindings ~expr))
You can use this the fail->
macro just like the ->
macro. I use it
to handle HTTP routes. Honestly, I’m not ecstatic about fail->
as a
name.
(fail-> (get-person person-id)
resource)
Where get-person
would return a not-found
failure for a bad
person-id
, and resource
uses a protocol to construct something
that Compojure can render as a response.
I really should offer m->
to algo.monads
.