你怎么在Clojure做letcc?

时间:2016-05-23 12:03:22

标签: clojure scheme continuations

在书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做什么?

2 个答案:

答案 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
  • " native"之间的无缝通话(未转化的)和转换后的代码
  • 例外(try / catch / finally)支持
  • binding表格(他们也适当地尾递归!)
  • 允许您提供现有本机功能的CPS版本(如果您想在该功能中捕获延续,这是必需的)

替代方案包括:

  • delimc提供了一个用于分隔连续的库。这似乎并不完整(例如,binding失败,因为它不了解try / finally块)并且没有被触及4年。
  • algo.monads是Clojure的monad库。 monad和continuation之间存在强大而有趣的关系,而algo.monads提供了一个延续monad。尽管monadic风格并不是很方便,但它确实具有使效果更明确的优点,这有助于封装使用不具有代码的控制效果的代码。另外,do符号(例如,domonad宏)极大地模糊了直接和一元风格之间的界限。

答案 1 :(得分:3)

示例中(letcc hop ...)捕获的延续被用作"向上延续"。可以使用名称return代替:(letcc return ... (return () ...)。当调用名为return的连续符时,整个letcc表达式求值为return赋予的值 - 然后作为intersectall的结果返回。

这意味着1.继续上升(我们返回)和2.继续只使用一次。满足这些条件后,您就可以像letcctry那样实施catch

因此,在我看来,通过编写letcc宏,您已回答了自己的问题。

现在Nathan Davis提到还有其他用例的延续,但Clojure并不直接支持它们。

注意:此处有一个相关问题:The Seasoned Schemer, letcc and guile