解释器的名字
有一位大佬曾经说过:“计算机科学只有两个难题:命名和缓存失效。”
所以解释器目前还没有名字。
暂时用Inf这个名字吧,Infinity的意思。
如何做到代码数据统一
作为Lisp的方言,Scheme也是一款在灵魂深处就把代码和数据融为一体的语言。因此Inf在设计的时候,也有意识地贯彻了这种“代码即数据”的思想。
列表是Scheme的核心数据结构,也是Inf目前提供的唯一数据结构。作为一种数据结构,列表在Inf内部是按照地址进行引用的。由于函数式语言的纯函数特性,许多涉及表的操作并不是在传入表的基础上做修改,而是生成新表输出。也就是说,尽管Inf内部使用地址对表进行引用,但语言提供的表操作接口,却是传值引用的。
Inf程序涉及两类列表:其一是首先载入内存的代码和出现在代码中的表,包括quote列表和作为代码的S-List;其二是程序运行过程中,作为中间结果或者最终结果产生的临时表。
对于第一类列表,无论是quote还是S-List,都是按照AST遍历顺序,以AST节点的形式,在内存中统一编址、线性存储的。第二类列表是中间结果,与第一类列表共用相同的地址空间,即AST节点所在的空间。
例如,表操作(cons 'a '(b))
,返回的新表'(a b)
并不是在原来的表'(b)
的基础上做修改,而是在内存中创建了一个新表'(a b)
,并将其(地址)返回。
表'(b)
是代码中出现的静态数据,而新表'(a b)
是程序运行时产生的中间结果。二者同为列表,尽管生存时期不同,但本质上都是列表。因此,将包括代码在内的所有列表统一编址,将大大简化解释器实现。
从实现的角度考虑,两类列表共用相同的逻辑地址空间,但是在物理上未必共同存储。如果考虑到安全性问题,可以对第一类列表进行访问控制。如果要获得最大的灵活性,则可以将两类列表等同看待,甚至允许程序在运行时读取、乃至动态地修改自身代码,实现宏、反射等元编程能力。
垃圾回收
从上文cons
的例子可以看到,cons
是一个纯函数,执行过程中会产生大量的中间结果,如果不加清理,将很快耗尽内存。因此,必须引入垃圾回收机制,以回收不再被使用的死对象所占据的内存空间。
GC的对象?
一切运行时产生的数据对象,包括列表、闭包、字符串,都是垃圾回收的对象。由于列表地位特殊,因此目前只研究列表垃圾回收的实现。
辣鸡对象如何判定?
还是举例子:下面是一个简单的求和程序,用于求出列表中所有元素的和。为简单起见,不考虑类型检查等细节。
(define sum
(lambda (lst)
(if (null? lst)
0
(+ (car lst) (sum (cdr lst))))))
(sum '(1 2 3 4 5))
程序执行前,静态数据'(1 2 3 4 5)
已经被保存在代码区,这部分数据是不参与垃圾回收的。
执行sum函数,内部调用cdr,生成了一个新的中间结果'(2 3 4 5),作为递归调用sum的参数再次调用sum。随着递归调用的进行,内存中陆续产生了'(3 4 5)、'(4 5)、'(5)和'()这几个中间结果。最终,递归收敛,栈帧陆续退栈,结果15被计算出来并返回,但是刚才产生的5个中间结果,却留在了内存空间中,后续分配的内存,便不能使用这部分空间。这就是垃圾回收的对象。
在C/C++等提供指针/引用传递机制,甚至显式内存管理接口的语言中,程序员可以在代码中手动释放掉这些死对象。但是在Scheme中,只要是纯函数式的操作,逻辑上都是传值调用,语言层面上无法控制内存的分配。因此,辣鸡回收功能需要由语言内部(即解释器/运行时)去实现。