如何在Clojure中的try / catch块中保留状态更新

时间:2013-11-27 22:21:35

标签: clojure

所以我有一些我想要的东西,在try块中,向各个数据对象添加各种数据,然后在抛出异常的情况下,保存带有错误的记录,并在异常之前检索所有数据字段。在Java中,这很容易。即使你使用某种不可变类型的记录,你也可以这样做:

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);

所以,如果它在步骤C发生轰炸,那么它将保存A,B和错误的记录。

我无法在Clojure中找出任何直接的方法。如果在try附近放置let,则必须将记录的“更新”分配给每个新变量,因此它们不在catch表达式的范围内。即使它们是,你也不会知道使用哪一个。

我想我可以对每个表达式进行try / catch / let,但这比Java版本的代码要多得多,并且需要在任何地方复制save语句。我的理解是,Clojure的简洁性和轻松避免重复是很好的,所以有些东西让我觉得这是错误的方法。

当然这是一个相当普遍的需求,并且有一个简单的惯用解决方案,对吗?

1 个答案:

答案 0 :(得分:6)

我认为包装每一个语句实际上是最常用的解决方案。如果你不想写太多,你可以构建一个宏来为你的单个步骤添加异常处理。

(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#))))))

此行为与->类似,但如果调用error-fn块,则会在当前值(和异常)上调用catch

(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}

当然,您可以随时使用可变状态,例如一个atom,但我不认为如果你能用一点宏观的话来达到同样的效果。