在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
根据维基百科的引用,手动实现 call/cc 需要满足以下两个前提条件:
- 语言必须支持闭包。
- 必须使用延续传递风格(CPS)编写程序。
我猜你可能不喜欢第二个条件。
要使用延续传递风格编写程序:
- 每个函数都必须接受一个延续参数。
- 函数必须通过调用它们的延续来返回。
因此,使用 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 形式是非常繁琐的,参见详细信息。
重点短语是
可以使用“过程传递风格”(continuation-passing style, CPS)实现程序
(强调为我所加。)通过一个称作 CPS 变换的程序转换,你可以将常规的“直接风格”程序转换为 CPS。关键在于 call/cc
的 CPS 变换是一个简单的函数。
这对于程序员来说不切实际。CPS 变换有两个用途:
- 作为研究语言特性,尤其是控制运算符的理论思想
- 作为编译器的传递,在使用 CPS 作为中间语言的情况下
你不想手动对 Lua 代码进行 CPS 变换,尤其是接近不切实际的。
回答关于在 Lua 中使用 call/cc 的计划的问题:Lua 中没有使用 call/cc 的计划。捕获 continuation 要么太昂贵,要么需要一些比 Lua 编译器更高级的代码分析。还存在一个问题是 Lua 的 continuation 可能包含 C 的部分。
然而,使用 coroutine,我们可以在 Lua 中实现 call/cc1 (一次性 continuation)。这已经足够满足许多 continuation 的用途。
以下是我在 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)))
可以实现:
TypeScript-to-Lua编译器 https://github.com/roblox-ts/roblox-ts + JS中的call-cc https://github.com/zaoqi/callcc.js/blob/master/callcc.js
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
我猜你忘了在延续传递风格中编写程序的部分。一旦你这样做了,在 Lua 或任何其他语言中,调用 call/cc 就是微不足道的,因为延续将成为所有函数的显式参数(包括 call/cc)。
PS:除了闭包,你还需要合适的尾调用来以延续传递风格编程。