我正在尝试理解延续传递样式转换。
我试图建立一个方案到C编译器,我想使用延续传递风格。不管延续传递样式是不是正确的方法你们能解释一下转换规则吗?
例如,以下是Gambit Scheme的创造者Marc Feeley的演讲。
我将总结他给出的延续传递样式规则,但注意:我不理解它们。特别是我不明白C是什么。
符号如下:
[E]
C
,表示延续c中表达式E的延续传递样式转换。
例如,一个转换规则是这样的:[c]
C
==>
(C c) ;; application of C to c
,表示连续c中常数c的CPS转换。
C是什么?我很难理解C是什么。这就像魔法一样。
[(if E1 E2 E3)]
C
==>
[E1]
(lambda (r1)
(if r1 [E2] [E3]))
C C
E1传递给r1
但是C是什么?
你们能解释一下吗?
谢谢。
如果您向上滚动到第7页,您将发现延续是什么,这对于理解转换为延续传递样式的规则是必要的。一个例子是
> (sqrt (+ (read) 1))
,它注意到(read)
的延拓是
取一个值,加1,计算它的平方根,打印结果并进入下一个REPL交互。
因此表达式E的延续C是"无论该表达式的值发生什么变化"。这一点在第20页得到了重申,示例
(let ((square (lambda (x) (* x x))))
(write (+ (square 10) 1)))
,其中(square 10)
的延拓为
(lambda (r) (write (+ r 1)))
因此,当您递归地将程序转换为CPS风格时,随着您深入到表达式,延续C将增长。注意,每个翻译规则[E]|C
的结果是一个较小的未翻译的E,如果E足够简单,可能是空的,和一个较大的翻译的c。
当您在CPS中转换代码时,您实际上在求值中引入了严格的规程。
当你写(+ x y z)
时,你计算+,x, y, z的顺序是不清楚的。如果你写的语言明确地定义了一个顺序,你知道会发生什么。但是,如果语言没有插入顺序,您可以通过将代码写入/转换为CPS来定义您希望的顺序,在我建议的示例中,您将写入:
(eval + (lambda (+)
(eval x (lambda(x)
(eval y (lambda (y)
(eval z (lambda (z)
(+ x y z))))
如果你想求左右值。
如果你用CPS写代码,这就像用汇编语言写代码一样,因为每段代码都可以与一条指令相关联,而这条指令在非常低的编程中具有相应的指令。如果在CPS中转换某些代码,则需要唯一地重命名变量以避免冲突。在创建C语言的时候,我认为它没有明确定义CPS转换;这就是inline
说明符拒绝递归调用的原因。通过重写C代码并使用CPS转换,可以将递归函数转换为goto-loop
,但标准C不希望这样做。
将代码转换为CPS的方法有很多。例如,在Mit-scheme中,输入代码没有显式地以CPS形式重写,但评估过程使用goto
语句和trampoline调用的组合来模拟它(这是一种在学校里学不到的方法,但在实践中使用)。
递归CPS代码可以直接转换为迭代循环(这就是为什么scheme->C翻译器首先进行转换)来解决尾部递归。Dan Friedman的EPL第一版详细介绍了这一点。弗里德曼也有一篇关于这方面的文章。如果你找不到,我会试着帮你找。