Functional programming 在SECD机器中,“如何;rap";工作

Functional programming 在SECD机器中,“如何;rap";工作,functional-programming,evaluator,abstract-machine,Functional Programming,Evaluator,Abstract Machine,我在C#中写了一篇文章,是在C#的指导下写的。我已经完成了基本操作,但我不确定如何实现rap指令 在维基百科上,它说的是关于rap的内容: rap的工作原理与ap类似,只是它用当前环境替换了虚拟环境的出现,从而使递归函数成为可能 对于ap它说: ap从堆栈中弹出一个闭包和一个参数值列表。闭包通过将其环境安装为当前环境、将参数列表推到该环境前面、清除堆栈并将C设置为闭包的函数指针来应用于参数。上一个值S、E和下一个值C保存在转储中 下面是我的ap public void ap()

我在C#中写了一篇文章,是在C#的指导下写的。我已经完成了基本操作,但我不确定如何实现
rap
指令

在维基百科上,它说的是关于rap的内容:

rap的工作原理与ap类似,只是它用当前环境替换了虚拟环境的出现,从而使递归函数成为可能

对于
ap
它说:

ap从堆栈中弹出一个闭包和一个参数值列表。闭包通过将其环境安装为当前环境、将参数列表推到该环境前面、清除堆栈并将C设置为闭包的函数指针来应用于参数。上一个值S、E和下一个值C保存在转储中

下面是我的
ap

    public void ap() 
    { 
        Push(S, ref D); 
        Push(E, ref D); 
        Push(C, ref D); 
        List closure = Pop(ref S);
        List paramlist = Pop(ref S);
        E = closure.Tail;
        Push(paramlist, ref E);
        C = closure.Head;
        S = List.Nil;
    }
请注意,
List
是我对Lisp风格的“cons”单元格的实现


让我困惑的是
rap
ap
到底有什么不同?例如,环境寄存器(E)到底发生了什么?我发现维基百科的定义有点模棱两可,并且没有找到任何其他的解释

SECD不是尾部递归的,尽管您可以构建

AP指令用于编译“let”绑定,而RAP指令用于编译“letrec”绑定

“letrec”与“let”不同,因为您可以“查看”定义递归函数的环境,以便可以递归调用它(例如,您可以定义一个“阶乘”函数,并可以在函数体中调用它)

RAP通过调用rplaca(类似于Scheme中的setcar!)来修改环境。先前的DUM指令将“虚拟”汽车添加到环境中(从未使用过),RAP将删除环境中的该“虚拟”汽车,并将其替换为相应的汽车

状态转换如下所示:

((c'.e')v.s) e (AP.c) d => NIL (v.e') c' (s e c.d) ((c'.e')v.s) (?.e) (RAP.c) d => NIL (setcar! e',v) c' (s e c.d) ((c'.e')v.s)e(AP.c)d=> 无(v.e')c'(s e c.d) (c''e')v.s.(?.e)(RAP.c)d=> 零(setcar!e',v)c'(s e c.d) 另见,引述:

RAP指令用于支持递归函数调用,其工作原理是将堆栈上先前创建的虚拟环境(称为OMEGA)替换为包含递归作用域中可见的所有函数的虚拟环境。规范使用RPLACA表示替换操作,这也是我们在SECD的Lisp实现中使用的:。。。 及

当我试图在Erlang中实现RAP时,我陷入了困境,因为列表上没有破坏性操作。不在标准API中,似乎也不在系统API中。因此,Erlang SECD看起来不错,只是它没有运行。
你真的应该买一本彼得·亨德森的精彩小书《函数式编程的应用和实现》。在这本书中,他仔细地描述并构建了一台SECD机器和Lispkit-Lisp。

除了公认的优秀答案之外,我还想提供更多关于为什么需要
rap
的解释

环境(在
SECD
中的
E
)存储所有持久化实体(函数、常量、变量等)
E
本质上是一堆列表。
E
中的内容加载到堆栈
S
,然后由
C
中的命令执行或使用。
E
中的所有内容都有一个id,以便以后可以引用。此ID通常是一个元组
(x,y)
,其中
x
表示列表在堆栈上的位置,
y
表示列表中的位置

在典型的函数调用中,在
E
上推送一个新列表,现在任何局部变量都可以有
(|E |,y)
这样的ID,其中
|E
表示
E
的大小。但是,对于递归函数来说,这是非常有问题的,因为堆栈大小随着每次函数调用而增加,因此无法分配局部变量


为了解决这个问题,
rap
完成了
ap
所做的大部分工作,但它没有将新列表推到环境堆栈上,而是用新的环境列表替换
E
开头的任何内容

作为参考,这里有一个完整的SECD机器,大约100行F#,然后是一个编译的LispKit Lisp,并在大约100行中进行测试。