【问题标题】:How to retain state updates within try/catch blocks in Clojure如何在 Clojure 的 try/catch 块中保留状态更新
【发布时间】:2013-11-28 06:46:19
【问题描述】:

所以我有一些我想要的东西,在一个 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 因其简洁性和易于避免重复而非常出色,所以有些事情让我认为这是错误的做法。

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

【问题讨论】:

    标签: clojure


    【解决方案1】:

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

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

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

    (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,但如果你能用一点宏福来达到同样的效果,我认为你不应该这样做。

    【讨论】:

    • 优秀!我认为这应该是宏如何有用并允许其他情况下不可能的事情的代表性演示(此构造具有 true 不变性)。或者添加到clojure.core。我想回想起来你可以在 Scala 中用flatmap 做同样的事情,但它会不那么漂亮。
    • 惯用 Clojure 代码的杰出示例。这个答案应该会得到更多的赞!
    • 这样做的缺点是错误处理程序不能依赖于绑定其他人然后是失败的确切形式之一。
    猜你喜欢
    • 1970-01-01
    • 2018-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多