2013-11-27 74 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

這個缺點是,錯誤處理程序不能依賴於綁定其他人,然後失敗的確切形式之一。 –