【问题标题】:call/cc in Lua - Possible?在 Lua 中调用/抄送 - 可能吗?
【发布时间】:2011-02-19 03:14:30
【问题描述】:

Continuation 上的维基百科文章说:
“在任何支持闭包的语言中,都可以编写连续传递风格的程序并手动实现 call/cc。”

要么是真的,我需要知道该怎么做,要么是不正确的,需要更正该陈述。

如果这是真的,请告诉我如何在 Lua 中实现 call/cc,因为我看不到如何实现。

我想如果 Lua 有 coroutine.clone 函数,如here 所述,我可以手动实现 call/cc。

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

下面的文字是可选的阅读。
P.S.:Lua 的协程表有一次性延续。 coroutine.clone 函数将允许我克隆它以多次调用它,从而有效地使 call/cc 成为可能(除非我误解了 call/cc)。然而,该克隆功能在 Lua 中不存在。 Lua IRC 频道上有人建议我使用 Pluto 库(它实现序列化)来编组一个协程,复制它,然后解组它并再次使用它。虽然这可能会奏效,但我更感兴趣的是 call/cc 的理论实现,以及找到一种语言需要具备的实际最小功能集以允许其手动实现。 p>

编辑 1: 好的,大家帮帮我,这花了我很长时间,因为我不知道任何方案,但我想出了一些可以帮助我们的方法。请看下面的代码。第一个是 Scheme 中的程序,第二个是同一个程序,但在 Lua 中。
希望这会帮助我们。我相信我们已经非常接近了。

P.S.:这些示例取自the Wikipedia article on CallCC 上的第一个示例。 方案版本

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

; callcc CPS-transformed (thanks to the people from the #scheme channel at freenode.net)
(define cpscallcc
  (lambda (consumer k)
    (let ((cc (lambda (result) (k result))))
      (consumer cc k))))

; this is the continuation we will use to display the "returned" values
(define main-continuation
  (lambda (result)
    (display "--> ")
    (display result)
    (newline)))

; define f function non-CPS
(define (f return)
  (return 2)
  3)

; these are my past attempts at defining a CPS f function
;(define (cps-f return k)
;  (k (return 2)) 3)
;(define (cps-f return k)
;  (k (lambda ()
;       (return 2)
;       3)))

; this is what I came up with - I'm not sure if this is correctly CPS-transformed but I     believe so
(define (cps-f return k)
  (return 2)
  (k 3))

; call the non-CPS f function
(display (f (lambda (x) x))) ; displays 3
(newline)

; call the non-CPS f function with call/cc (I don't understand what this does)
(display (call/cc f)) ; displays 2
(newline)

; now call the CPS version of the f function
(cps-f (lambda (x) x) main-continuation)  ; displays --> 3

; now call the CPS version of the f function with the CPS version of call/cc
(cpscallcc cps-f main-continuation)  ; displays --> 2 but then it also displays --> 3 afterwards -> I'm not sure why it displays the 3 afterwards, as it should only display the 2 just like the non-CPS versions above



Lua 版本

-- callcc CPS-version
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

-- define f function non-CPS
f = function(ret)
    ret(2)
    return 3
end

-- define f function CPS-version (again, not sure this is correct)
cps_f = function(ret, k)
    ret(2)
    k(3)
end

-- call the non-CPS f function
print(f(function(x) return x end))

-- we cant call the non-CPS f function with callcc because
-- Lua doesnt have callcc, but the line below displays the correct expected output (maybe by accident)
--cpscallcc(f, print)

-- now call the CPS version of the f function
cps_f( function(x) return x end, print )  -- displays 3

; now call the CPS version of the f function with the CPS version of call/cc
cpscallcc( cps_f, print) -- displays 2 and then 3 just like the Scheme version!!
-- so apparently the translation from Scheme to Lua is correct...



我正在使用适用于 Windows 的 DrScheme 和 Lua - 对于任何想要帮助解决这些问题的人来说,这是两个易于下载和安装的工具。

【问题讨论】:

    标签: lua continuations callcc


    【解决方案1】:

    根据维基百科的引述,手动实现 call/cc 有两个先决条件:

    1. 语言必须支持闭包
    2. 您必须以连续传递样式 (CPS) 编写程序

    我怀疑你不会喜欢 #2。

    以连续传递风格编写程序:

    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 是非常繁琐的;见details here

    【讨论】:

    • 哇,你让我非常接近理解这一点。你能扩展 callcc 函数的定义吗?特别是通过解释允许它记住保存/记住所有状态的部分,就像 Scheme 的 call/cc 所做的那样。
    • 我想我在如何使用这个 callcc 函数时遇到了麻烦 - 在 Scheme 中,您必须为 call/cc 设置一个跳转到的位置,但在 CPS 中,您不会......这个我想是让我失望。
    • 由于 k 是 callcc 将返回的闭包,将它传递给它的参数 f 完成了 call/cc 的工作 [我刚刚编辑了上面的定义,因为我忘记了正确返回]。当程序在 CPS 中时,这是微不足道的,因为延续总是可用的;在 Scheme 中它是隐藏的,并且 call/cc 的工作更难(在语言级别,实现在编译器中可能同样微不足道),因为它必须“具体化”延续。
    • 好的,现在你的 callcc 定义等同于 Scheme 的吗?意思是,这是捕获延续的函数,以便我以后可以调用它吗?我一直认为“callcc”这个名称具有误导性(至少对于它在 Scheme 中的使用方式而言)。
    • 是的,callcc 等价于 Scheme 的,除了延续是显式参数(因为程序在 CPS 中)而不是隐式参数(因为 Scheme 使用直接样式)。
    【解决方案2】:

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

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

    【讨论】:

    • 首先,很荣幸您能回答我的问题。我完全爱上了你的语言,原因有很多(小核心、C 集成、非常一致的语法、真正的闭包、一流的函数等)。现在回答:PS澄清了很多。如果我要求在 Lua 中提供 call/cc 的示例实现,因为 Lua 允许闭包和尾调用优化,这会不礼貌吗? :)
    • 我同意 Norman Ramsey 在下面的回复。我想一个更好的问题是,有没有计划在 Lua 中实现一流的延续并因此调用cc? ;)
    • 好点。我编辑了维基百科以澄清正确的尾声。关于“尾递归”的文章令人震惊……
    【解决方案3】:

    回答有关 Lua 中 call/cc 计划的问题:Lua 中没有 call/cc 计划。捕获一个延续要么太昂贵,要么需要一些代码分析,远远超出了 Lua 编译器的能力。还有一个问题是 Lua 延续可能包含 C 中的部分。

    但是,使用协程,我们已经可以在 Lua 中实现 call/cc1(一次性延续)。这对于许多延续的使用来说已经足够了。

    【讨论】:

    • 我能理解。那么,有没有关于 coroutine.clone 的计划,就像 Mike Pall 在我原始帖子中的链接上实施的计划一样?
    • 有关 Lua 中 callcc 的工作示例,使用实现 coroutine.clone 的修改后的 Lua,请参见:github.com/torus/lua-call-cc/blob/master/callcc.lua。我很快就会尝试一下。 :)
    【解决方案4】:

    关键词是

    可以以延续传递风格实现程序

    (强调我的。)您通过采用常规的“直接式”程序并通过称为CPS 转换的程序转换将它们转换为连续传递式(CPS)来做到这一点。关键是call/cc的CPS变换是一个简单的函数。

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

    • 作为研究语言特征,尤其是控制算子的理论思想
    • 作为使用 CPS 作为中间语言的编译器的传递

    您不想在 Lua 代码上进行 CPS 转换,尤其是手动转换。

    【讨论】:

    • 您可能不想使用 CPS 编写整个应用程序,但以这种方式实现单个算法来解决子问题是完全可行的。
    • call/cc 的 CPS 转换是一个简单的函数 - 小心! call/cc 可以通过一个简单的函数在 CPS 转换的目标中定义,但它不是任何东西的 CPS 转换,因为它不会通过调用其延续来返回。
    • 问题是“如何”而不是“应该”。
    【解决方案5】:

    有可能: TypeScript 到 Lua 编译器 https://github.com/roblox-ts/roblox-ts + 在 JS 中调用-cc https://github.com/zaoqi/callcc.js/blob/master/callcc.js

    【讨论】:

    【解决方案6】:

    这是我的 cps-convert in scheme,只需将您要转换的每个函数都传递给它。

    (define (cps-convert function . functions)
      # Since "help" is called at 2 different places...
      (define (help) (error "syntax: (cps-convert f1 f2 ...)"))
      # Single function converter
      (define (convert func)
        # "name" contains the function's name prefixed with "cps-"
        (let ([name (string->symbol
                              (string-append "cps-" (symbol->string func)))])
          # Dirty hack to define "cps-*" in the global environment
         `(eval '(begin
                       # Necessary to prevent the function from being evaluated
                       (define ,name #f)
                                    # Magic
                       (set! ,name (lambda (k . args) (k (func args)))))
                     # Global environment
                     (interaction-environment))))
      # Prerequisite... Call help if condition not met
      (if (symbol? function)
          # function is a symbol
          (cond
            # If there is only one function to convert
            [(null? functions) (convert function)]
            # Else ensure every other "functions" are symbols and convert each
            [(every symbol? functions) (apply convert function functions)]
            # Oops! Condition not met!
            [else (help)])
          # Else clause from previous "if"
          (help)))
    

    【讨论】:

    • 这与问题无关。
    猜你喜欢
    • 2015-03-27
    • 2023-03-20
    • 2013-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-25
    • 2011-04-02
    相关资源
    最近更新 更多