The heart of your question seems to be in your comment:
Yeah, maybe I should explain what about this is "ugly": It's the not-well composability of just return an actual result, which can be represented almost arbitrarily and a numbered keyword as error. My callstack looks like this:
(-> msg msg-handler step-handler step-N sub-function)
, and all of them could return this one error type, but none of them returns the same success type and another thing is, that I have to manually design if en error return type should short-circuit or has to be expected to do some intermediate work (undo data changes or notify other clients in parallel)
Clojure 1.5+ has the some->
threading macro to get rid of nil?
check boilerplate. We just need to gently adjust the code to substitute the nil?
check for a check of our choice.
(defmacro pred->
"When predicate is not satisfied, threads expression into the first form
(via ->), and when that result does not satisfy the predicate, through the
next, etc. If an expression satisfies the predicate, that expression is
returned without evaluating additional forms."
[expr pred & forms]
(let [g (gensym)
pstep (fn [step] `(if (~pred ~g) ~g (-> ~g ~step)))]
`(let [~g ~expr
~@(interleave (repeat g) (map pstep forms))]
~g)))
Note with this definition, (pred-> expr nil? form1 form2 ...)
is (some-> expr form1 form2...)
. But now we can use other predicates.
Example
(defn foo [x] (if (even? x) :error-even (inc x)))
(defn bar [x] (if (zero? (mod x 3)) :error-multiple-of-three (inc x)))
(pred-> 1 keyword? foo) ;=> 2
(pred-> 1 keyword? foo foo) ;=> :error-even
(pred-> 1 keyword? foo foo foo) ;=> :error-even
(pred-> 1 keyword? foo bar foo bar) ;=> 5
(pred-> 1 keyword? foo bar foo bar foo bar foo bar) ;=> :error-multiple-of-three
Your use case
A flexible choice would be to make a wrapper for validation errors
(deftype ValidationError [msg])
Then you can wrap your error code/messages as in (->ValidationError 4002)
and change your threading to
(pred-> msg #(instance? ValidationError %)
msg-handler step-handler step-N sub-function)