Reputation: 10781
So I've got something where I want to, within a try block, add various data to some data object, and then in case an exception is thrown, save that record with the error and all data fields retrieved before the exception. In Java this'd be easy. Even if you go with some kind of immutable type of record, you can do:
MyRecord record = new MyRecord();
try {
record = record.withA(dangerouslyGetA());
record = record.withB(dangerouslyGetB());
record = record.withC(dangerouslyGetC());
} catch (Exception ex) {
record = record.withError(ex);
}
save(record);
So if it bombs at step C, then it'll save record with A, B and the error.
I can't figure out any straightforward way to do this in Clojure. If you put a try
around a let
then you have to assign the record's "updates" to new variables each, and thus they aren't in scope in the catch
expression. And even if they were, you wouldn't know which one to use.
I guess I could put a try/catch/let around each expression, but that's a lot more code than the Java version and requires duplicating the save
statement everywhere. My understanding was that Clojure was great for its terseness and easy avoidance of duplication, so something makes me think this is the wrong way to go.
Certainly this is a fairly common need and has a simple idiomatic solution, right?
Upvotes: 4
Views: 324
Reputation: 6073
I think wrapping every single statement is actually the most idiomatic solution to this. And if you don't want to write too much, you can construct a macro to add exception handling to your single steps.
(defmacro with-error->
[error-fn value & forms]
(if-not (seq forms)
value
`(let [ef# ~error-fn
v# ~value]
(try
(with-error-> ef# (-> v# ~(first forms)) ~@(rest forms))
(catch Exception ex# (ef# v# ex#))))))
This behaves like ->
but will call error-fn
on the current value (and the exception) if the catch
block is invoked:
(with-error-> #(assoc % :error %2) {}
(assoc :x 0)
(assoc :y 1)
(assoc :z (throw (Exception. "oops.")))
(assoc :a :i-should-not-be-reached))
;; => {:error #<Exception java.lang.Exception: oops.>, :y 1, :x 0}
Of course, you could always do it with mutable state, e.g. an atom
, but I don't think you should if you can achieve the same with a little bit of macro-fu.
Upvotes: 6