lisp解释器实现 (3)
讲述如何实现匿名函数。
函数结构体
我们在使用一个函数的调用的时候,实际在函数退出的时候,函数里面所使用的局部变量都没有再使用了。可见一个函数调用的时候是自带一个环境的。
这个环境最初跟外面的环境就差别不大,主要是多了一个函数本身带有的参数项。通过我们上一次定义的binding函数就能做出来这样一个环境了。
需要说明的是,这个环境并不会再随着外界的环境变化而发生变化,不然会招致很多的bug。
在Clojure里,如下代码:
1 | (let [x 5] ) |
会输出15。试想的是如果在函数里面的x随着环境变化而变化,这个函数将会输出30,这样中间没有隔着其他语句还好,如果隔了好几百行,你忘了前面用过x,于是重新定义了这个x,然后调用函数func。这简直就是一场灾难,人在码后面,bug前面来。
所以一个函数本质上来说,应该至少包含三个东西:
1 | (defstruct Cls :env :function :arg) |
即其函数体、参数、环境。这个结构体名字应该叫做闭包(Closure
)
函数定义
定义一个函数的时候,我们只需要解析这个函数的相关信息,然后构造一个结构体就好了:
在调用这个函数的时候比较第一项是否为'lambda
1 | (defn funcDef |
其中arg1、arg2都是定义的函数。
1 | (defn arg1 [s] (second s)) |
分别是取列表的第234项。
函数调用
在调用一个函数的时候,我们需要为函数准备好它所需要的环境,其实也就是将参数也放到环境中去,然后我们则只需要在这个环境中解析函数体就可以了,这个递归调用解释器的其他部分即可。
由于调用传入的参数也可能是一个表达式的形式,我们也需要去解析这个东西,只要递归调用解释器函数就好了。
1 | (_interp (cls :function) |
Clojure实现
现在我们要实现的是这样一种东西,这相当于一个匿名函数:
1 | (lambda <arg> <expr>) |
arg放的是函数的参数,expr放的是函数的表达式。
为了简单起见,我们所实现的函数将只接受一个参数。这样并不会造成什么问题,因为我们可以定义这样的式子来表示多参数的函数。
1 | (lambda <arg1> (lambda <arg2> <expr>)) |
调用的时候像下面这种形式即可:
1 | (((lambda <arg1> (lambda <arg2> <expr>)) <value1>) <value2>) |
我们的解释器函数_interp
需要包括两个参数:就是表达式和表达式所处的环境
当然在最开始我们需要一个空环境,这个可以写一个interp函数作为_interp的封装:
1 | (defn interp |
emptyEnv 的定义在上一篇中有提到。
_interp函数
这个函数不过是把前面所说的综合起来而已。不过也就是这个解释器的主体了。
1 | (defn _interp |
op?
的定义是这样的(这个函数写得很丑可能是一大败笔)
1 | (defn op? |
完整的代码在这里