讲述如何实现匿名函数。

函数结构体

我们在使用一个函数的调用的时候,实际在函数退出的时候,函数里面所使用的局部变量都没有再使用了。可见一个函数调用的时候是自带一个环境的。

这个环境最初跟外面的环境就差别不大,主要是多了一个函数本身带有的参数项。通过我们上一次定义的binding函数就能做出来这样一个环境了。

需要说明的是,这个环境并不会再随着外界的环境变化而发生变化,不然会招致很多的bug。

在Clojure里,如下代码:

1
2
3
4
5
(let [x 5] )
(defn func [y] (* x y))

(def x 10)
(func 3)

会输出15。试想的是如果在函数里面的x随着环境变化而变化,这个函数将会输出30,这样中间没有隔着其他语句还好,如果隔了好几百行,你忘了前面用过x,于是重新定义了这个x,然后调用函数func。这简直就是一场灾难,人在码后面,bug前面来。

所以一个函数本质上来说,应该至少包含三个东西:

1
(defstruct Cls :env :function :arg)

即其函数体、参数、环境。这个结构体名字应该叫做闭包(Closure)

函数定义

定义一个函数的时候,我们只需要解析这个函数的相关信息,然后构造一个结构体就好了:

在调用这个函数的时候比较第一项是否为'lambda

1
2
3
(defn funcDef
[env exp]
(struct Cls env (arg2 exp) (arg1 exp)))

其中arg1、arg2都是定义的函数。

1
2
3
(defn arg1 [s] (second s))
(defn arg2 [s] (second (rest s)))
(defn arg3 [s] (second (rest (rest s))))

分别是取列表的第234项。

函数调用

在调用一个函数的时候,我们需要为函数准备好它所需要的环境,其实也就是将参数也放到环境中去,然后我们则只需要在这个环境中解析函数体就可以了,这个递归调用解释器的其他部分即可。

由于调用传入的参数也可能是一个表达式的形式,我们也需要去解析这个东西,只要递归调用解释器函数就好了。

1
2
3
4
5
(_interp (cls :function)
(binding
(cls :arg)
(_interp value env)
env))

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
2
3
(defn interp
[exp]
(_interp exp emptyEnv))

emptyEnv 的定义在上一篇中有提到。

_interp函数

这个函数不过是把前面所说的综合起来而已。不过也就是这个解释器的主体了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(defn _interp
[exp env]
(cond
(number? exp) exp
(symbol? exp) (getValue exp env)

(list? exp)
(cond
(= (count exp) 2)
(let
[cls (_interp (first exp) env)]
(_interp (cls :function)
(binding (cls :arg)
(_interp (second exp) env)
(cls :env))))

(and (= (count exp) 3) (= (first exp) 'lambda))
(funcDef env exp)

(and (= (count exp) 4) (= (first exp) 'let))
(_interp (arg3 exp)
(binding (arg1 exp)
(arg2 exp)
env))

(op? (first exp))
((symbol_map (first exp))
(_interp (arg1 exp) env)
(_interp (arg2 exp) env))

:else (prn "error!"))
:else (prn "error!")))

op?的定义是这样的(这个函数写得很丑可能是一大败笔)

1
2
3
4
5
6
7
8
9
(defn op?
[sym]
(cond
(= sym '+) true
(= sym '-) true
(= sym '*) true
(= sym '/) true

(true) false))

完整的代码在这里