一开始的时候,我只是希望她能递归的执行计算,比如n的阶乘:
(define fact (lambda (n)
(if (< n 1) 1 (* n (fact (- n 1))))))
看到这么多括号,我猜你已经被自动劝退了。但是相信我,这只是为了省掉写parser的时间。所以,只需要加一点点语法糖,她就可以变成下面的样子:
(define-function fact(n)
(if (< n 1)
1
(* n (fact (- n 1)))
))
跟Java很像不是么~
从一个计算器开始
所以,她和大学时代你用C写的那个计算器有什么区别?
从本质上来说其实并没有。
回忆一下,你从stdin读进来3 + 4
,然后解析出+
、3
、4
这几个元素,接着 把加法作用于3
和4
的值 。
为了方便,我们可以让输入的格式是形如:(* (+ 1 2) (+ 3 4))
的样子,所以你依然把输入解析为*
、(+ 1 2)
、(+ 3 4)
,然后继续用刚才的方式 把乘法作用于(+ 1 2)
和(+ 3 4)
的值 。
让我们简单抽象一下,输入的表达式(Expression)可以分为几个部分:
(operator expression1 expression2)
(这里的第一个参数operator,实际上也可以是expression)
因此,她的内核,实际上就只有一行代码:
eval(expression) = apply(operator,
eval(expression1),
eval(expression2)
)
表达式expression的求值结果,等于把算子作用于子表达式1和子表达式2的值。
我相信我能体会到约翰麦卡锡在60年前,第一次看到她时候的震撼。
表达式
前面讲到,她是基于表达式求值的,也就是说每一行源代码都会有一个求值结果。
作为一个递归函数,肯定会有一个终止条件,求值的终止条件,在于某个子表达式是原子类型。
所以我们把表达式,细分成了原子表达式和复合表达式。
复合表达式如:(+ 1 2)
或者如一个变量定义(define pi 3.14)
。它的求值逻辑在前面已经讲过了。
原子表达式如:数字1
,求值结果依然是数字1
,字符串foo
,求值结果依然是字符串foo
。
上下文
刚才介绍原子表达式的时候,我们忽略了一种情况,如果是变量,比如pi,应该怎么办?
实际上我们应该尝试去获取变量的定义,也就是尝试从上下文(或者说环境变量)中取值。
那么上下文又是什么?
实际上你可以把它简单的理解成一个Map
(在没有函数调用的情况下,这样的解释总是说的通)。
每当对define相关的表达式进行求值时,比如(define pi 3.14)
,实际上,就是把这一个键值对设置进上下文中。
(还记得我们的apply函数吗?这就是define关键字apply的结果,也可以说是define的语义。)
因此,当尝试对于pi
进行求值时,实际上是去Map里根据key去查value。