Skip to content

Instantly share code, notes, and snippets.

@laurentpetit
Created May 25, 2011 13:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save laurentpetit/990975 to your computer and use it in GitHub Desktop.
Save laurentpetit/990975 to your computer and use it in GitHub Desktop.
Apply let bindings 'til reaching the body, unless an intermediate value ...
=> (def ^{:private true :macro true} assert-args #'clojure.core/assert-args)
#'user/assert-args
=> (defmacro
let-unless
"Apply the bindings and then the body like clojure.core/let would do, unless error-fn,
applied to any intermediate result, returns logical true,
in which case no more binding will be evaluated, and the return value will be the result
of applying handler-fn to the result of error-fn"
{:arglists "([error-fn handler-fn bindings & body])"}
[error-fn handler-fn bindings & body]
(assert-args let-unless
(vector? bindings) "a vector for its binding")
(assert-args let-unless
(even? (count bindings)) "an even number of forms in binding vector")
(if-not (seq bindings)
`(do ~@body)
(let [[binding-form expr & more] bindings]
`(let [error-fn# ~error-fn
handler-fn# ~handler-fn
value# ~expr
~binding-form value#]
(if (error-fn# value#)
(handler-fn# value#)
(let-unless error-fn# handler-fn# ~(vec more) ~@body))))))
#'user/let-unless
=> ;; simulating the maybe monad
=> (let-unless nil? (constantly "no intermediate result can be nil!") [r1 :foo r2 (println "one") r3 (println "two")] :success)
one
"no intermediate result can be nil!"
=> ;; usage of custom error results (of type MyError)
=> (defrecord MyError [msg exc])
user.MyError
=> (defn error [x] (when (= (type x) MyError) x))
#'user/error
=> (defn handler [err] (str "no intermediate result can be of type MyError:" err))
#'user/handler
=> (let-unless error handler [r1 nil _ (do (println "one") (MyError. "Gasp!" nil)) r3 (println "foo")] :success)
one
"no intermediate result can be of type MyError:user.MyError@7594e821"
=> ;; error/handler functions can be inline or not
=> (defn handler [err] :failure)
#'user/handler
=> (let-unless error handler [r1 nil _ (MyError. "Gasp!" nil) r3 :step-3] :success)
:failure
=> ;; the macroexpansion looks like:
=> (pprint (macroexpand '(let-unless error handler [r1 nil _ (MyError. "Gasp!" nil) r3 :step-3] :success)))
nil
(let*
[error-fn__794__auto__
error
handler-fn__795__auto__
handler
value__796__auto__
nil
r1
value__796__auto__]
(if
(error-fn__794__auto__ value__796__auto__)
(handler-fn__795__auto__ value__796__auto__)
(user/let-unless
error-fn__794__auto__
handler-fn__795__auto__
[_ (MyError. "Gasp!" nil) r3 :step-3]
:success)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment