头图

几个月前,用rust写了第一版的lisp 解释器,里面混合了很多lisp方言的语法,还扩展实现了多线程等,这次是打算基于r7rs规范实现一个更规范的Scheme方言的lisp解释器。

Scheme 是lisp 的一个方言,简约美观,易于学习和实现,lisp的全称是列表处理器(LISt Processor) 的缩写。

在开始编写Scheme解释器前,我们先看几个scheme 程序或者也可以通过 r7rs-overview对Scheme 有个大体的印象。

编写程序时有点像玩积木,如果我们打算搭建一个还不错的积木房子,我们应该了解一下手头有什么形状的积木,单个如何使用,多个如何组合;编程亦是如此,首先在一门语言中他有哪些基础元素 ,这些基础元素如何使用 ,如何将这些基础元素组合成我们需要的形状和功能,重复的功能如何复用。

通常在程序中会有最基础的三种不同的数据类型 数字 布尔 和字符,在scheme中他们是可以这样表示。

;; 数字 
1 
;; 布尔
#f
;; 字符
#\a

有了数字布尔和字符之后,我们通常会对其施加某种操作,以数字举例 :

1 + 2

通常用来表示 数字1加数字2,在计算器中按下这组符号后再按下 = 按钮时会得到数字3的结果;

我们将 此处的+称其为函数,1和2称其为参数,在lisp 中 函数的位置会放在首位 如 1 + 2 在lisp中要转换为

+ 1 2

现在我们写出了lisp中第一个程序,让我们对比一下 函数在中间 和 函数在开头 的区别 ,看似没有区别,这里对刚才的1+2表达式多增加几个参数

1 + 2 + 3 + 4 + 5 + 6

在lisp中对应的表达式是

 + 1 2 3 4 5 6 

现在区别就比较明显了,在参数存在多个的情况下 lisp中的这种前缀表达式相对于中缀表达式写法更加简洁也利于程序处理。

但对于编写程序如果只是 ++ -- 也太过与简单了,不过它具备通用性,这种通用性贯穿于lis前缀表达式的深处,现在让我们面对一个新的问题 比较数字的大小,在开始之前我们先学习一下lisp 中if语法:

if 条件 表达1 [表达式2]

上面表达式的含义是 当条件成立时 执行表达式1 否则 执行表达式2,表达式2可为空

当我们在 lisp输入以下表达式

if (> 1 2) 1 2

控制台会输出2

那比较两个数字大小的这组表达式如何复用呢

通常我们可以定义一个函数如下面的伪代码

let max = (x, y) => (
    if (x > y) {
        x
    } else {
        y
    }
)
let a = max(1,2)

在lisp中可以通过lambda声明创建一个函数通过define 给这个表示式起个名

(define max (lambda (x y) (
    if (> x y) x y
)))
(define a (max 1 2))

现在我们有了第一个自己定义的函数 max, 它和+ 是一样的级别与类型,在lisp中管它叫 produce ,它的作用是用于求最大值,当然也可以不显式声明函数名称直接调用

((lambda (x y) (
    if (> x y) x y
)) 1 2)

这种叫匿名函数 调用。

现在我们已经学会了最基础的数据类型和函数声明 、变量定义 、函数调用及分支,已经可以解决一些简单的问题了,接下来我们在介绍一个(func 函数/ data 复合数据类型) cons ,它用于声明创建一个对,可以通过 car 函数获取第一个元素 通过 cdr获取第二个元素 ,通过学习cons 我们会发现数据还是函数之间的界限是模糊的,让我们开始吧。

用我们前面学到的知识既可以做到实现一个cons

(define cons (lambda (x y) (
    (lambda (f) (f x y))
)))

现在也许晕了," (lambda (f) (f x y))" 这是什么? 他表达了返回一个函数这个函数会接收一个函数作为参数 作为参数的函数会被调用传入x 和 y的值。这有点绕,后面讲到作用域的实现这个问题就会迎刃而解,现在只需要记住这个函数被调用后会f函数会被调用从而接收到外部函数 x y 对应的值即可。
让我们定义一个car 函数

(define car (lambda (g) (
    (g (lambda (a b) a))
)))

" (g (lambda (a b) a))" 这又是什么? 先用代入替换的方式 ,把g这个变量替换成入参"(lambda (f) (f x y))"

((lambda (f) (f x y)) (lambda (a b) a))

这句表达式的含义是前面的函数被调用参数f是 (lambda (a b) a), 对上面的表达式再次用代入替换的方式替换f这个变量会得到下面的表达式

((lambda (a b) a) x y)

这句表达式的含义是接收两个参数 a b 返回a
然后再次用代入替换发将x y 替换成 1 2 会得到如下表达式

((lambda (a b) a) 1 2)

这句表达式的含义是函数接收数字 1 2 返回第一个数字

现在再回到

(car (cons 1 2))

入参 "(cons 1 2 )“ 会产生一个"(lambda (f) (f x y))"函数,形参 x y可以用代入替换为实参 1 2 得到表示式 "(lambda(f) (f 1 2))", 现在会得到下面的表达式

(car (lambda(f) (f 1 2)))

"(car (lambda(f) (f 1 2)))" 会产生一个表达式 “(g (lambda (a b) a))” 将g这个形参替换成实际传入的值 "(lambda(f) (f 1 2))",会得到表示式

((lambda(f) (f 1 2)) (lambda (a b) a))

f形参再次替换成实参 "(lambda (a b) a)“ 会得到表示式

((lambda (a b) a) 1 2)

上面有讲到过这个 ,这里不在赘述。
现在开始编写 cdr 函数,cdr 和car 函数类似唯一区别是一个返回第一个参数,一个返回最后一个参数

(define cdr (lambda (g) (
    (g (lambda (a b) b))
)))

现在我们已经开始能定义复合类型了,但没有修改功能,接下来我们对cons 进行改造让其支持修改,需要用到 set! 表达式 它的格式是:

set! 变量名 表达式

修改cons

(define cons (lambda (x y) (
    (lambda (f) (
        f x y (lambda (a) (set! x a)) (lambda (b) (set! y b))
    ))
)))

修改car

(define car (lambda (g) (
    g (lambda (x y sx sy) x)
)))

修改cdr

(define car (lambda (g) (
    g (lambda (x y sx sy) y)
)))

定义 set-car!

(define set-car! (lambda (g a) (
    g (lambda (x y sx sy) (sx a))
)))

定义 set-cdr!

(define set-car! (lambda (g b) (
    g (lambda (x y sx sy) (sy b))
)))

现在可以使用 上面定义的五个函数了

(
    (define c (cons 1 2))
    (set-car! c 3)
    (car c))

运行后会得到3。

截止目前为止,已经了解了lisp 中的 基础数据类型 分支 函数声明 函数调用 还有变量定义 及 变量修改,并实现了一个复合的数据类型,和针对该类型的函数, 后面会介绍输入和输出,当介绍完输入输出之后这门语言最简单的基础内容就介绍完了,后面要掌握的是大量的针对不同数据类型提供的内置函数操作及不同形式的组合的函数等

学习其他语言也是如下

有哪些基础元素,这些基础元素如何使用 ,如何将这些基础元素组合成我们需要的形状和功能,重复的功能如何复用

yangrd
1.3k 声望225 粉丝

代码改变世界,知行合一。