2016-05-23 91 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