在Lua中使用call/cc是否可能?

维基百科关于Continuation的文章说:

"在任何支持闭包的语言中,都可以以 continuation passing style 的方式编写程序并手动实现 call/cc。"

要么这是真的,我需要知道如何做到这一点,要么这是不正确的,需要纠正这个说法。

如果这是真的,请告诉我如何在 Lua 中实现 call/cc,因为我无法看出如何做到这一点。

如果闭包不足以实现 call/cc,那么还需要什么?

以下内容是可选择阅读的。

P.S.:Lua 有一次性 continuation 的功能,使用该协程表的 coroutine.clone 函数可以克隆多个协程,从而有效地实现 call/cc(除非我误解了 call/cc)。不过 Lua 中没有这样的克隆功能。Lua IRC 频道上的某些人建议我使用 Pluto 库(它实现了序列化)来编组协程,将其复制,然后取消编组并重新使用。虽然这可能有效,但我更感兴趣的是 call/cc 的理论实现,并找出语言中实际需要的最少的功能集,以实现 call/cc 的手动实现。

编辑 1: 好的人们,在这里帮助我,这花了我很长时间,因为我不知道 Scheme,但我想出了一些应该会帮助我们的东西。请查看下面的代码。第一个是 Scheme 中的程序,第二个是相同程序的 Lua 版本。

希望这将对我们有所帮助。我相信我们离目标很近。

P.S.:这些示例取自维基百科关于 CallCC 的第一个示例

Scheme 版本

(define call/cc call-with-current-continuation)

; callcc CPS-transformed (感谢 #scheme 频道的人们)
(define cpscallcc
  (lambda (consumer k)
    (let ((cc (lambda (result) (k result))))
      (consumer cc k))))

; 这是我们将用来显示“返回”值的 continuation
(define main-continuation
  (lambda (result)
    (display "--> ")
    (display result)
    (newline)))

; 定义非 CPS 的 f 函数
(define (f return)
  (return 2)
  3)

; 我过去尝试定义 CPS f 函数的方式
;(define (cps-f return k)
;  (k (return 2)) 3)
;(define (cps-f return k)
;  (k (lambda ()
;       (return 2)
;       3)))

; 我想出了这个 - 我不确定这是否是正确的 CPS 转换,但我相信是的
(define (cps-f return k)
  (return 2)
  (k 3))

; 调用非 CPS 的 f 函数
(display (f (lambda (x) x))) ; 显示 3
(newline)

; 使用 call/cc 调用非 CPS 的 f 函数(不知道它是什么意思)
(display (call/cc f)) ; 显示 2
(newline)

; 现在调用 CPS 版本的 f 函数
(cps-f (lambda (x) x) main-continuation)  ; 显示 --> 3

; 现在使用 CPS 版本的 call/cc 调用 CPS 版本的 f 函数
(cpscallcc cps-f main-continuation)  ; 显示 --> 2,但然后还会显示 --> 3 -> 我不确定为什么会显示 3,因为它只应该像上面的非 CPS 版本一样显示 2

Lua 版本

``` -- callcc CPS 版本 cpscallcc = function(consumer, k) local cc = function(result) return k(result) -- ?or k(result) end return consumer(cc, k) -- ?or return consumer(cc,k) end

-- 定义非 CPS 的 f 函数 f = function(ret) ret(2) return 3 end

-- 定义 CPS 版本的 f 函数(同样,不确定这是否正确) cps_f = function(ret, k) ret(2) k(3) end

-- 调用非 CPS 的 f 函数 print(f(function(x) return x end))

-- 我们无法使用非 CPS 的 f 函数调用 call/cc,因为 -- Lua 没有 call/cc,但下面的行只显示了正确的预期输出(也许是偶然的) --cpscallcc(f, print)

-- 现在调用 CPS 版本的 f 函数 cps_f( function(x) return x end, print ) -- 显示 3

; 现在使用 CPS 版本的 call/cc 调用 CPS 版本的 f 函数 cpscallcc( cps_f, print) -- 显示 2 然后显示 3,就像方案版本一样! -- 所以,显然从方案到此处的翻译

原文链接 https://stackoverflow.com/questions/2827620

点赞
stackoverflow用户340461
stackoverflow用户340461

我猜你忘了在延续传递风格中编写程序的部分。一旦你这样做了,在 Lua 或任何其他语言中,调用 call/cc 就是微不足道的,因为延续将成为所有函数的显式参数(包括 call/cc)。

PS:除了闭包,你还需要合适的尾调用来以延续传递风格编程。

2010-05-13 16:04:12
stackoverflow用户33252
stackoverflow用户33252

根据维基百科的引用,手动实现 call/cc 需要满足以下两个前提条件:

  1. 语言必须支持闭包。
  2. 必须使用延续传递风格(CPS)编写程序。

我猜你可能不喜欢第二个条件。

要使用延续传递风格编写程序:

  1. 每个函数都必须接受一个延续参数。
  2. 函数必须通过调用它们的延续来返回。

因此,使用 k 作为延续参数的函数将是这样的:

function multiplyadd(k, x, y, z) return k(x * y + z) end

顶层函数可能会使用 print 作为它的延续,因此在顶层调用 multiplyadd 将看起来像:

multiplyadd(print, 2, 4, 1)

有了这个基础,我们可以定义 call/cc:

function callcc(k,f) return f(k,k) end

请注意,上面的 multiplyadd 实际上在作弊,因为 *+ 没有使用 CPS。将所有操作符转换为 CPS 格式、将所有 Lua 库函数替换为 CPS 等价函数,并将所有代码翻译 / 生成为 CPS 形式是非常繁琐的,参见详细信息

2010-05-13 16:28:08
stackoverflow用户41661
stackoverflow用户41661

重点短语是

可以使用“过程传递风格”(continuation-passing style, CPS)实现程序

(强调为我所加。)通过一个称作 CPS 变换的程序转换,你可以将常规的“直接风格”程序转换为 CPS。关键在于 call/cc 的 CPS 变换是一个简单的函数。

这对于程序员来说不切实际。CPS 变换有两个用途:

  • 作为研究语言特性,尤其是控制运算符的理论思想
  • 作为编译器的传递,在使用 CPS 作为中间语言的情况下

你不想手动对 Lua 代码进行 CPS 变换,尤其是接近不切实际的。

2010-05-13 21:27:49
stackoverflow用户341452
stackoverflow用户341452

回答关于在 Lua 中使用 call/cc 的计划的问题:Lua 中没有使用 call/cc 的计划。捕获 continuation 要么太昂贵,要么需要一些比 Lua 编译器更高级的代码分析。还存在一个问题是 Lua 的 continuation 可能包含 C 的部分。

然而,使用 coroutine,我们可以在 Lua 中实现 call/cc1 (一次性 continuation)。这已经足够满足许多 continuation 的用途。

2010-05-14 16:56:09
stackoverflow用户1044454
stackoverflow用户1044454

以下是我在 Scheme 中编写的 cps-convert 函数,只需将想要转换的每个函数作为参数传递给它。

(define (cps-convert function . functions)
  # 因为在 2 个不同的位置调用了“help”函数...
  (define (help) (error "syntax: (cps-convert f1 f2 ...)"))
  # 单个函数转换器
  (define (convert func)
    # "name" 包含带有 "cps-" 前缀的函数名称
    (let ([name (string->symbol
                          (string-append "cps-" (symbol->string func)))])
      # 污渍的技巧是在全局环境中定义 "cps-*"
     `(eval '(begin
                   # 必要的,以防止函数被评估
                   (define ,name #f)
                                # Magic
                   (set! ,name (lambda (k . args) (k (func args)))))
                 # 全局环境
                 (interaction-environment))))
  # 先决条件... 如果不满足条件,调用 help 函数
  (if (symbol? function)
      # 函数是一个符号
      (cond
        # 如果只有一个要转换的函数
        [(null? functions) (convert function)]
        # 否则,确保每个其他的“functions”都是符号并将其转换
        [(every symbol? functions) (apply convert function functions)]
        # 哎哟!条件不满足!
        [else (help)])
      # 上一个“if”的 else 子句
      (help)))
2011-11-21 16:29:15
stackoverflow用户11852092
stackoverflow用户11852092

可以实现:

TypeScript-to-Lua编译器 https://github.com/roblox-ts/roblox-ts + JS中的call-cc https://github.com/zaoqi/callcc.js/blob/master/callcc.js

2019-07-29 12:01:06