Lua 中的 call/cc - 可能吗?

发布于 2024-09-01 10:03:36 字数 3630 浏览 10 评论 0原文

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

要么这是真的,我需要知道如何去做,要么这不是真的,需要更正该说法。

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

我想如果 Lua 有 coroutine.clone 函数,我就可以手动实现 call/cc,如 此处

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

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

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

PS:这些示例取自有关 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 - 对于任何想要提供帮助的人来说,这是两个易于下载和安装的实用工具。

The Wikipedia article on Continuation says:

"In any language which supports closures, it is possible to write programs in continuation passing style and manually implement call/cc."


Either that is true and I need to know how to do it or it is not true and that statement needs to be corrected.


If this is true, please show me how to implement call/cc in Lua because I can't see how.


I think I'd be able to implement call/cc manually if Lua had the coroutine.clone function as explained here.


If closures are not enough to implement call/cc then what else is needed?

The text below is optional reading.

P.S.: Lua has one-shot continuations with its coroutine table. A coroutine.clone function would allow me to clone it to call it multiple times, thus effectively making call/cc possible (unless I misunderstand call/cc). However that cloning function doesn't exist in Lua. Someone on the Lua IRC channel suggested that I use the Pluto library (it implements serialization) to marshal a coroutine, copy it and then unmarshal it and use it again. While that would probably work, I am more interested in the theoretical implementation of call/cc and in finding what is the actual minimum set of features that a language needs to have in order to allow for its manual implementation.

EDIT 1: Ok people, help me out here, this took me a long time because I don't know any Scheme, but I came up with something that should help us out. Please look at the codes below. The first one is a program in Scheme, the second one is the same program but in Lua.
Hopefully this will help us out. I believe we are very close.


P.S.: These examples are taken from the first example on the Wikipedia article on CallCC.
Scheme version

(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 version

-- 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...




I'm using DrScheme and Lua for Windows - for anyone that wants to help up out those are two easy to download and install tools that just work.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

流绪微梦 2024-09-08 10:03:36

根据维基百科的引用,手动实现 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< /code> 实际上作弊,因为 *+ 不在 CPS 中。以 CPS 形式添加所有运算符,用 CPS 等效项替换所有 Lua 库函数,并将所有代码翻译/生成为 CPS 是非常繁琐的;请参阅此处的详细信息

There are two prerequisites to manually implement call/cc per the Wikipedia quote:

  1. the language must support closures
  2. you must write your program in continuation passing style (CPS)

I suspect you will not like #2.

To write your program in continuation passing style:

  1. Every function must take a continuation argument
  2. Functions must return by calling their continuation

So, using k as the name of the continuation argument, a function would look like:

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

The toplevel might use print as its continuation, so invoking multiplyadd at top level would look like:

multiplyadd(print, 2, 4, 1)

With that scaffolding we could define call/cc as

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

Note that the above multiplyadd actually cheats since * and + are not in CPS. It is very tedious to add all the operators in CPS form, replace all the Lua library functions with CPS equivalents, and translate/generate all your code to CPS; see details here.

挽容 2024-09-08 10:03:36

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

PS:除了闭包之外,您还需要对连续程序进行适当的尾部调用
传递风格。

I guess you forgot the part about writing your program in continuation passing
style. Once you do that, call/cc is trivial (in Lua or in any other language),
as the continuation will be an explicit parameter to all functions (call/cc
included).

PS: besides closures, you also need proper tail calls to program in continuation
passing style.

早茶月光 2024-09-08 10:03:36

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

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

Answering the question about plans for call/cc in Lua: There are no plans for call/cc in Lua. Capturing a continuation is either too expensive or require some code analsis well beyond what the Lua compiler can do. There is also the problem that Lua continuations may include parts in C.

With coroutines, however, we can already implement call/cc1 in Lua (one-shot continuations). That is good enough for many uses of continuations.

小姐丶请自重 2024-09-08 10:03:36

关键短语是

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

(重点是我的。)您可以通过采用常规“直接风格”程序并通过程序转换将它们转换为连续传递风格(CPS)来实现这一点称为CPS 变换。关键是call/cc的CPS变换是一个简单的函数。

。 CPS 变换有两个用途:

  • 作为研究语言特性(尤其是控制运算符)的理论思想
  • 作为使用 CPS 作为中间语言的编译器中的一个通道

这对程序员来说不切实际 想要对 Lua 代码进行 CPS 转换,尤其是不要手动进行。

The key phrase is

It is possible to implement programs in continuation-passing style

(Emphasis mine.) You do this by taking regular "direct-style" programs and converting them to continuation-passing style (CPS) by a program transformation called the CPS transform. The key is that the CPS transform of call/cc is a simple function.

This is not practical for programmers. The CPS transform has two uses:

  • As a theoretical idea for studying language features, especially control operators
  • As a pass in a compiler that uses CPS as an intermediate language

You don't want to go anywhere near doing CPS transforms on Lua code, especially not by hand.

凉风有信 2024-09-08 10:03:36

这是我的 cps-convert 方案,只需将您想要转换的每个函数传递给它即可。

(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)))

Here's my cps-convert in scheme, just pass it every function you want to convert.

(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)))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文