Lisp学习笔记——Common Lisp简单求值器的编写(2)
Common Lisp基础知识
关于语言的介绍,这里会按照上一篇文章所说的方法,对体系进行结构性的介绍。
因为这里的内容是后面的基石,所以写的比较小心,前前后后改了好几遍,尽量的描述清楚一点吧。
列表(list):由括号包围,并且可包含任何数量的由空格所分隔的元素。元素可以是原子或者列表。比如(1 2 3),(1 (2 3))
S-表达式(s-expression):原子和列表的总称。
过程:指为了完成一项任务,具体的一步步的实现方法的总称。一个函数就代表了一个过程。
注意:这里的列表是递归定义的。同时()代表空表,它既算列表也算原子,属于一个特殊情况。
首先,介绍的是语言的基本表达形式。
这一块,主要讲的是语言的基本元素,包括基本数据类型以及基本操作过程。就像构成世间所有物质的原子一样(学物理的同学不要鄙视我,大学就没学过物理了,一直停留在高中水平.....)
语言基本数据类型:
两大类:
原子(atom)——包括数字、字符串和符号等
列表(list)
语言基本七大基础操作过程:
这个大家可以参考我之前的文章,关于这一部分内容的翻译。 注意,在这个地方,我指的是过程而一,而不是指表达式或者函数体。大家可以这样来看,这里指的仅仅是一个变量,这个变量所存的内容是早就写好的一系列机器操作码,用来完成相应的过程。
语言的组合方式:
这块内容教给了我们组合上面介绍的基本元素的规则,来形成Lisp表达式的方法。每一个Lisp表达式都会有一个相应的值,当你把一个Lisp表达式输入到求值器当中,便能得到这个值。所以,Lisp程序就是由一个一个Lisp表达式组合而成的。所以Lisp语言也就是指这些Lisp表达式。注:有的地方又会把Lisp表达式叫做LISP形式,其实一样。
如何来构成LISP表达式呢?
很简单,首先原子就是一个LISP表达式。对于原子来说,数字和字符串属于自求值对象,也就是它们的值就是本身;符号的值是它所代表的变量的值,符号其实就是一个变量名罢了。但这只是Lisp表达式的一小部分而已。我们还可以这样来构成余下表达式:
1.取一对括号
2.取一个代表基本操作过程的符号或者函数名(后面介绍,属于抽象方法),作为第一个元素,称为运算符。
3.后面跟着运算符所代表的过程所需要的参数,这些参数是Lisp表达式。
大家可以看到,这个地方所定义的东西正好也是一个List,并且它只是LIST的一个子集而已。通过上面的规则,我们可以定义复杂的Lisp表达式,由基本的元素衍生出无穷无尽的LISP表达式。
前面提到每个Lisp表达式都会对应一个值,那求值规则又是怎样的呢?
很简单:
1.原子的话,字符串和数字自求值,符号则是它所代表的变量的值。
2.对于其他形式,则对Lisp表达式的参数从左到右依次求值,然后传递进操作符所代表的过程,进行运算。
这里要注意对第二点的理解。前面提到过,语言当中会有七种基本过程,它们是第二条规则运行的基石。所有复杂的操作最后都会被分解为对基本过程的调用。同时,这里还隐含了递归过程,对于Lisp表达式参数求值的时候,因为它也是一个Lisp表达式,所以它也适用于上面的规则,这样就可以一直递归下去,直到求值成功。这里,大家可以看到,递归的理念贯穿于整个LISP语言之中。很显然,在我们后面代码的实现过程中,递归是必不可少的。大家有兴趣的话可以看看我之前对递归的一个数学解释。 但是,在这里有一些特殊形式的求值规则是与此介绍的不一样的,后面会在求值器的实现中加以指名。
第三部分,抽象方法:
这一块的内容,是通过一种包装的方式让我们能够用简洁的形式去使用任意复杂的复合表达式。所谓抽象,即以一种形式将含有某类特征的事物包装起来成为一个新的单元,使用它的时候,我们可以直接使用这个单元,而不用去关心它的内部实现细节。它的如何实现不重要,只要能符合这类事物的特征即可。
在这里,我们使用一个符号去引用一个过程。不必考虑过程的实现细节,我们只要知道这个过程已经被实现,它能够完成相应功能即可。
为了达到这样的目的,LISP在这里分了两步完成:
1.如何定义一个过程?——这就是lambda表达式,这里大家也可以参考我的翻译。
通过过程定义,我们也定义了如何使用过程的方法,这就和大家平时见的函数调用差不多。这里我们可以用过程应用的代换模型来描述。
即对每个实际参数求值,再将过程体中的形式参数用实参体替代后,对形成的表达式求值。
2.如何对过程命名?
可以把过程当成一个值,然后赋予一个变量即可。
最后为了方便,我们把上述两部合成为一步:defun ,利用它,我们就可以将过程定义和命名融合在一起简洁的表达了。
这样一个清晰的体系就建立在我们的面前了,我们只需要实现这个体系的基本元素,并且让它可以按照自己的构建规则自我构造,那么我们就能够成功的实现这个体系?它难吗?So easy!