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)
get-person would return a
not-found failure for a bad
resource uses a protocol to construct something that Compojure can render as a response.
I really should offer