Unicorn -- 我的第一个玩具Lisp解释器

一开始的时候,我只是希望她能递归的执行计算,比如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,然后解析出+34这几个元素,接着 把加法作用于34的值

为了方便,我们可以让输入的格式是形如:(* (+ 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。

Lambda!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章