I have a few operations I want to thread where each can fail. I would much rather get the error as a value instead of using try-catch which breaks the flow of execution.
I can do the naive version and make my functions use nil as failure:
(if-let (op1 ...)
(if-let (op2 ...)
...
err1)
err2)
but this is nested and makes it harder to read.
I could use some->
which seems like the closest solution but it doesn't say what failed:
(if-let [res (some-> arg
op1
op2)]
res
somethin-failed) ;; what failed though?
I also looked at ->
, and cond->
but they don't seem to help.
I know there are macros online to do these kind of things but I would much rather not add macros if something exists to solve this. Hopefully there is something of the form:
(some-with-err-> arg
op1 err1
op2 err2
...)
I may be overlooking something simpler, but I can't seem to find something built-in to address this issue.
I can write a macro to do it but would rather avoid it for now.
There's nothing built-in for this, but there are libraries for monadic error handling (e.g. Failjure) which seems like what you're looking for.
You could derive a version some-with-err->
from the some->
macro definition. The only practical difference is the map
function that binds to steps
now partitions the forms/error values, wraps step
invocations in try
and returns a namespaced map on failure:
(defmacro some-with-err->
[expr & forms]
{:pre [(even? (count forms))]}
(let [g (gensym)
steps (map (fn [[step error]]
`(if (or (nil? ~g) (::error ~g))
~g
(try (-> ~g ~step)
(catch Exception _# {::error ~error}))))
(partition 2 forms))]
`(let [~g ~expr
~@(interleave (repeat g) (butlast steps))]
~(if (empty? steps)
g
(last steps)))))
It can be used like some->
but each form must be accompanied by an error return value:
(some-with-err-> 1
(+ 1) :addition
(/ 0) :division
(* 2) :multiplication)
=> #:user{:error :division}
(some-with-err-> " "
(clojure.string/trim) :trim
(not-empty) :empty
(str "foo") :append)
=> nil ;; from not-empty