2016-05-23 93 views
6

在書中The Seasoned Schemer - 作者寫道下面的代碼:你如何在Clojure中做letcc?

(define intersectall 
    (lambda (lset) 
    (letcc hop 
     (letrec 
      ((A (lambda (lset) 
       (cond 
        ((null? (car lset)) (hop (quote()))) 
        ((null? (cdr lset)) (car lset)) 
        (else 
        (intersect (car lset) 
           (A (cdr lset)))))))) 
     (cond 
      ((null? lset) (quote())) 
      (else (A lset))))))) 

這裏是潛在怎麼會看在Clojure中:

(defmacro letcc 
    [name & body] 
    `(letfn [(~name [arg#] 
      (throw (ex-info (str '~name) {:name '~name :value arg#})))] 
    (try [email protected] 
      (catch clojure.lang.ExceptionInfo e# 
      (if (= '~name (:name (ex-data e#))) 
       (:value (ex-data e#)) 
       (throw e#)))))) 

(defn intersectall 
    [lset] 
    (letcc hop 
    (letfn [(A [lset] 
      (cond (empty? (first lset)) 
        (hop()) 
        (empty? (rest lset)) 
        (first lset) 
        :else 
        (intersect (first lset) (A (rest lset)))))] 
    (cond (empty? lset) 
      () 
      :else 
      (A lset))))) 

我的問題是:你如何用Clojure做letcc

+1

這個問題有點不清楚。你在尋找一個比你更好的'letcc'還是你在想它有什麼問題?我認爲在Clojure中做「真正的」letcc是沒有可能的,因爲它沒有一流的延續。 – molbdnilo

回答

5

背景

核心的Clojure語言不支持一流的延續。這一點,以及JVM沒有提供捕捉當前延續的事實,意味着沒有辦法在所有情況下滿足letcc

但是,在某些情況下可以實施延續。特別是,如果你擁有所有的代碼(也就是你必須捕獲延續的代碼),那麼你可以使用延續傳球風格(CPS)。基本上,你爲每個函數添加一個額外的參數。該參數是表示該呼叫的繼續的函數。您通過調用continuation函數「返回」一個值。當然,這種風格本身是一種痛苦 - 但幸運的是,這是一種可以通過宏輕鬆應用於特定代碼的轉換。

就其本身而言,CPS不適用於不執行尾部呼叫優化(TCO)的平臺。因爲CPS中任何函數的最後一步都是調用另一個函數,所以沒有TCO,堆棧會很快溢出,除非是最簡單的計算。這個問題可以通過使用thunking和trampolining來解決。

解決方案

正如我上面提到,您可以編寫自己的CPS變換使用宏。不過,我會邀請您來結帳我的pulley.cps圖書館,它已經爲你做了這個。有替代品,但據我所知pulley.cps是唯一的Clojure庫,提供以下所有內容:之間

  • call-cc/let-cc
  • 無縫調用「原生」(非轉化)並轉化代碼
  • 異常(try/catch/finally)支持
  • binding形式(他們是正確的尾遞歸呢!)
  • 允許您提供CPS versio現有的原生功能的N(如果你要拍攝功能中的延續,這是必要的)

替代品包括:

  • delimc提供分隔延續庫。這看起來並不完全(例如,binding因爲不瞭解try/finally區塊而失敗),並且在4年內未觸及。
  • algo.monads是Clojure的monad庫。單子和連續之間有一個強大而有趣的關係,而algo.monads提供了一個連續單子。儘管monadic風格並不十分方便,但它具有使效果更加明確的優勢,這有助於封裝使用控制效果的代碼,而不使用代碼。另外,do表示法(例如,宏觀domonad)大大地模糊了直接和一元風格之間的界限。
+0

運行時支持tail調用或調用/ cc語言以使其具有這些功能,因爲它可以在編譯時將其僞裝,這並不是真正意義上的破壞。沒有運行時支持尾部調用和'call/cc'的硬件支持,因此在某種程度上它總是僞造的。 – Sylwester

+0

@Sylwester不確定你的意思。關於函數調用不能這樣說嗎?我的意思是,硬件可能有一個'call'指令(例如,x86架構),但編譯器/運行時仍然需要處理參數傳遞等。另外,關於TCO ... tail call是非尾部調用'call'就是'jmp'。所以我會說這兩種形式的函數調用同樣受硬件支持。但無論如何,答案的重點是你*不必爲了實現它們而在語言中內置延續。 –

+0

由於程序沒有參數,所以是或者兩者同樣由結果機器代碼模擬。我奇怪的是*沒有辦法實現letcc,所有情況都令人滿意*。我認爲我們有命名空間,並且可以映射每一個特殊的表單,所以應該有一種方法來導入一個連續庫,只需要'call/cc'就可以工作,而不需要引入新的特殊表單,但是會影響現有的表單當然。 – Sylwester

3

在您的示例中被(letcc hop ...)捕獲的延續被用作「向上延續」。可以使用名稱return代替:(letcc return ... (return() ...)。當名稱爲return的延續被調用時,整個letcc-expression會計算出給出的值return - 然後作爲intersectall的結果返回。

這意味着1.延續上升(我們返回)和2.延續只用一次。當滿足這些條件時,您可以像trycatch那樣執行letcc

因此,當我看到它,通過編寫您的letcc宏,你已經回答了你自己的問題。

現在Nathan Davis提到還有其他延續用例,但Clojure不直接支持它們。

注:這裏有一個相關的問題:The Seasoned Schemer, letcc and guile