在书The Seasoned Schemer中 - 作者编写了以下代码:
(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 ~@body
(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
做什么?
答案 0 :(得分:5)
核心Clojure语言不支持一流的延续。这一点,以及JVM没有提供捕获当前延续的方法的事实,意味着无法实现满足所有情况的letcc
。
但是,在某些情况下可以实现延续。具体来说,如果您拥有所有代码(即必须捕获continuation的代码),那么您可以使用continuation-passing-style(CPS)。基本上,您为每个函数添加一个额外的参数。此参数是表示该调用的延续的函数。你"返回"通过调用continuation函数的值。当然,这种风格本身很难写 - 但幸运的是,这是一种我们可以通过宏轻松应用于特定代码的转换。
CPS本身不适合不进行尾调用优化(TCO)的平台。因为CPS中任何函数的最后一步是调用另一个函数,没有TCO,堆栈会快速溢出,除了最简单的计算。这个问题可以通过采用thunking和trampolining来解决。
如上所述,您可以使用宏编写自己的CPS转换。但是,我会邀请您查看我的pulley.cps图书馆,该库已经为您完成此操作。还有其他选择,但据我所知,pulley.cps是唯一提供以下所有功能的Clojure库:
call-cc
/ let-cc
try
/ catch
/ finally
)支持binding
表格(他们也适当地尾递归!)替代方案包括:
binding
失败,因为它不了解try
/ finally
块)并且没有被触及4年。do
符号(例如,domonad
宏)极大地模糊了直接和一元风格之间的界限。答案 1 :(得分:3)
示例中(letcc hop ...)
捕获的延续被用作"向上延续"。可以使用名称return
代替:(letcc return ... (return () ...)
。当调用名为return
的连续符时,整个letcc表达式求值为return
赋予的值 - 然后作为intersectall
的结果返回。
这意味着1.继续上升(我们返回)和2.继续只使用一次。满足这些条件后,您就可以像letcc
和try
那样实施catch
。
因此,在我看来,通过编写letcc
宏,您已回答了自己的问题。
现在Nathan Davis提到还有其他用例的延续,但Clojure并不直接支持它们。
注意:此处有一个相关问题:The Seasoned Schemer, letcc and guile