overwatering.org

blog

about

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.