For the nested if-let mess, I'd probably do something like this:
(let-every [x (foo) err "foo failed"
y (bar x) err (format "bar %s failed" x)
z (goo x y) err (format "goo %s %s failed" x y)]
(qux x y z)
(handle-error err))
Where `let-every` is a macro that works like let, but stops short on the first nil/false variable, runs only the next symbol binding expression, and then runs the else-clause.
There'd be nothing special about the "err" symbol on each line. It's just the next symbol binding, but on the same line as a convenience, and this means it can reference any previously valid symbol bindings.
Here's a quick & dirty implementation of that macro. I don't have a Clojure interpreter installed, so I don't know if it works.
(defmacro let-every [bindings if-body else-body]
(let [pairs (partition 2 bindings)
quad-pairs (partition 2 pairs)]
(loop [quad-pairs quad-pairs]
(if quad-pairs
(let [[quad-pair] quad-pairs
[try-pair err-pair] quad-pair
[try-sym try-expr] try-pair
[err-sym err-expr] err-pair]
`(if-let [~try-sym ~try-expr]
(recur (next quad-pairs))
`(let [~err-sym ~err-expr]
~else-body)))
if-body))))
Given the above example, it should expand to this:
(if-let [x (foo)]
(if-let [y (bar x)]
(if-let [z (goo x y)]
(qux x y z)
(let [err (format "goo %s %s failed" x y)]
(handle-error err)))
(let [err (format "bar %s failed" x)]
(handle-error err)))
(let [err "foo failed"]
(handle-error err)))
There'd be nothing special about the "err" symbol on each line. It's just the next symbol binding, but on the same line as a convenience, and this means it can reference any previously valid symbol bindings.
Here's a quick & dirty implementation of that macro. I don't have a Clojure interpreter installed, so I don't know if it works.
Given the above example, it should expand to this: