2013-11-27 38 views
4

因此,我已经在try块内为某些数据对象添加了各种数据,然后在发生异常的情况下保存记录错误和异常之前检索的所有数据字段。在Java中,这很容易。即使你以某种一成不变类型的记录去,你可以这样做:如何在Clojure中的try/catch块中保留状态更新

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因其简洁和容易避免重复而出色,所以有些东西让我觉得这是错误的路要走。

当然这是一个相当普遍的需求,有一个简单的习惯解决方案,对吧?

回答

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)) [email protected](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} 

当然,你总是可以用可变状态做到这一点,例如一个​​,但我不认为你应该如果你可以用一点点宏福来达到同样的效果。

+0

杰出!我认为这应该是一个有代表性的示例,说明宏如何有用,并允许其他事情不可能发生(这种构造具有*真*不变性)。无论是或添加到'clojure.core'。我想回想一下,你可以在Scala中用'flatmap'完成同样的事情,但是它不会那么漂亮。 –

+0

惯用Clojure代码的杰出例子。这个答案应该得到更多的喜欢! –

+0

这个缺点是,错误处理程序不能依赖于绑定其他人,然后失败的确切形式之一。 –