曾经翻译整理的一篇LISP语言的入门文章,与大家分享. (请勿转载)
----------------------------------------------------------------
熟悉人工智能语言LISP程序设计
LISP语言的特性:
LISP语言是过去所有现存语言中最接近函数式语言的一种语言。J.McCarthy在1960年提出的最初的LISP语言完全是函数型的,后来为改善在传统计算机上的执行效率,就在流行的LISP版本中,把非应用式特性加入了语言中。LISP语言具有如下特性:
LISP程序的通常形式是一串函数定义,其后跟着一串带有参数的函数调用,函数之间的关系只是在调用执行时才体现出来。LISP中没有语句概念,也没有分程序结构或其他语法结构。语言中的一切成分都是以函数的形式给出。
在纯LISP中只有很少几个原始函数,虽然现有的LISP系统已增加了大量的内部函数,但这些新增加的函数都可以用最初的原始函数来表示。
在LISP中,程序和数据在形式上是等价的。LISP的唯一数据结构是S-表达式(表),而程序本身也是用S-表达式写的,因此可以把程序当作数据来处理,也可以把数据当作程序来执行。
递归是LISP的基础,是语言的主要控制结构,它不像大多数程序设计语言那样以迭代(循环)作为主要控制结构。LISP的递归处理是基于递归定义的数据结构。
LISP的数据结构——S-表达式
LISP是一种适合于符号处理的语言,它与一般高级语言有着很大的不同。LISP处理的唯一对象是符号表达式。这种符号表达式又称S-表达式,这里S代表符号。因此,LISP程序是对符号表达式进行加工和处理的。
原子是S-表达式的最简单情况,它可分为符号原子和数原子。符号原子是以字母开头的字母数字串,可用来表示变量、常量和函数的名字等。数原子是一串数字,在其前面可冠以符号‘-’或‘+’,分别表示负数原子和正数原子。
S-表达式定义如下:
原子是S-表达式。
如果S1和S2是S-表达式,则(S1*S2)也是S-表达式。
我们把(S1*S2)称为S-表达式的点对表示,S1、S2分别称为S-表达式的头部和尾部。应该注意,这个定义是一个递归定义。
S-表达式的表表示法:
表表示法的一般形式为:(<
S-表达式>…<
S-表达式>)其中,每个可以为原子,也可以为表。例如,(A ( B C ) ( D )
)是表。表中有三个元素,即一个原子A,两外两个是表(B C)和( D
),叫做子表。不难看出,表的结构是嵌套的,定义是递归的。我们把最外层表中元素的个数定义为该表的长度。例如,表(
A ( B C ) ( D ) )的长度为3。表元素是按次序排的,所以表是有序的,比如( A B C )不同于( B C A).
在表表示法中有一种特殊情况。若表的长度为0,亦即表中没有任何元素时,此表称空表,可写作()或NIL。
点对表示法转换成表表示法有如下两条规则:
( A * NIL ) * ( A ) 也即 ( A * ( ) ) * ( A )
( A * ( B C
D … H )) * ( A B C D … H
)
这表明若点对的右部是NIL时,可把圆点与NIL去掉,剩下的部分构成一新表。若点对的右部是一张表时,此时称混合表,则可把点与右部表的括号去掉,剩下部分组成一张新表。在转换中,原子仍保持原来形式。
LISP的基本函数
LISP中函数调用的一般形式为:
(函数名 自变量1
自变量2 …… 自变量n)
其中函数名为符号原子,每个自变量可为下列六种形式之一。
我们约定:S 代表S-表达式
L 代表表
F 代表函数
A 代表原子
NA 代表非原子
N 代表数原子
其基本函数有如下几个:
( CAR NA )
一元选择函数。自变量为非空表。函数CAR取自变量的第一个元素(即表的头部)。
例 (CAR “( ( A B C) X Y Z )
) * ( A B C )
( CAR “M )
出错,自变量为原子,函数无意义。
如果CAR作用于点对形式的自变量时,则取其左部。
( CDR NA )
一元选择函数。自变量为非空表。函数CDR回送一张表,这张表包含了自变量中除第一元素以外的所有元素,即表的尾部。所以它是CAR的补函数。
例: (CDR “( ( A B C) X Y Z )
) * (X Y Z)
如果CDR作用于点对形式的自变量时,则取其右部。当CDR作用于只含一个元素的表时,则CDR的结果为空表。
( CONS S1 S2 )
二元构造函数,第一个自变量与第二个自变量分别是S-表达式。当第二个自变量为一张表时,则函数CONS回送一张表,该表的CAR是第一个自变量,它的CDR是第二个自变量。
例: ( CONS “A “( B C )
) * ( A B C )
( ATOM S )
一元谓词函数。自变量为S-表达式。函数ATOM是一个判断自变量是否为原子的谓词。当自变量为原子时,则函数值为T;当自变量为非原子的S-表达式时,则函数值为NIL。
例: ( ATOM “HAPPY )
* T
( EQ A1 A2 )
二元谓词函数。两个自变量均为原子。函数EQ检验两个自变量,若为相同原子,则EQ函数值为T,否则为NIL。
例: ( EQ “AB “CD ) * NIL
( EQ “(AB) “(AB) ) * NIL 虽然两个自变量为相同的S-表达式,但它们不是原子,所以结果仍为NIL。
( NULL S )
一元谓词函数。其自变量为S-表达式。函数NULL判别自变量是否为空表,是空表时回送T,否则回送NIL。
例:( NULL “( ) ) * T
( QUOTE S )
一元函数。自变量为S-表达式。函数QUOTE回送的值就是跟在它们后面的S-表达式。换句话说QUOTE相当于符号“,是禁止求值的意思。
在解决实际问题时,常需要把基本函数加以复合。例如,要取出表( X Y
Z )中的第二个元素,需要先进行一次CDR运算,舍去第一个元素,然后再进行CAR运算,把剩下的表( Y Z
)中的第一个元素取出来,即:
( CAR ( CDR “( X Y Z ) ) )*( CAR “( Y Z ) )*Y
赋值函数和EVAL函数
( SET A S )
二元函数。第一个自变量为符号原子,第二个自变量为S-表达式。函数SET把第二个自变量的值赋给第一个自变量,并以第二个自变量的值作为SET函数的值。
( SETQ A S )
二元函数。第一个自变量为符号原子,第二个自变量为S-表达式。函数SETQ的作用与SET一样,只是不对第一个自变量求值。
( EVAL S )
EVAL函数对S-表达式求值。因为在调用一函数时,若自变量未加引号的话,则要对自变量求值。因此,在这种情况下,EVAL函数是对一个函数自变量求值之后,再求一次值。
其它表处理函数
( APPEND L S )
二元函数。第一个自变量为表,第二个自变量为S-表达式。当第二自变量为表时,则函数APPEND将两个自变量联成一个表,即它只是把两个表的元素放在一个表中;当第二自变量为原子时,则函数APPEND把第一个自变量表与第二自变量原子组成点对形式的S-表达式。
( LIST S1
S2 …… Sn
)
多元函数。每个自变量为S-表达式。函数LIST构成一个新表,它的元素是所有的自变量本身。
( LENGTH L )
一元函数。自变量为表。函数LENGTH求出自变量中最外层元素的个数,或者说求表L
的长度。
( REVERSE L )
一元函数。自变量为表。函数REVERSE把自变量表中元素(指最外层元素)顺序倒排。
( SUBST S1 S2
S3)
三元函数。每个自变量都为S-表达式。函数SUBST将S3表达式中出现的S2表达式用S1表达式来代替。更直观些,可写为( SUBST
)
( LAST NA)
一元函数。自变量为非原子的表。函数LAST回送一张表,其中只含有自变量表中的最后一个元素。
( EQUAL S1 S2
)判断两个S-表达式是否相同,如果相同则值为T,否则为NIL。
( MEMBER S1 S2
)测试S1表达式是否为S2表达式中的元素,如果是则值为T,否则为NIL。
算术运算函数与逻辑运算函数
( PLUS N1 N2 ……
Nk)实现K个数相加。
( DIFFERENCE N1 N2)或(DIFF N1 N2
)实现N1减N2。
( MINUS N )函数MINUS改变自变量值的符号。
( TIMES N1 N2 N3 ……
Nk)实现K个数相乘。
( QUOTIENT N1 N2 )实现N1除以N2,结果值为商。
( REMAINDER N1 N2 )取N1除以N2的余数。
( DIVIDE N1 N2 )结果值为N1除以N2的商和余数组成的表。
(NOT S )一元谓词函数。自变量为S-表达式。当自变量为原子NIL时才回送T,否则回送NIL。
( AND S1 S2 … Sn
)
( OR S1 S2 … Sn
)
条件函数
条件函数COND
LISP对如下结构的条件分支
If p1
then e1 else
If p2
then e2 else
…
If pn then en else
en+1
采用[p1*e1;
p2*e2; …;
pn*en;
T*en+1]
以上的形式在LISP中称为元语言表达式或M_表达式。上述分支的M_表达式,用S-表达式表示则写成如下形式:
( COND L1 L2 …
Ln+1)其中每个Li有表形式(pi
ei)。
COND函数依次对各表的P求值。
递归与迭代
我们把函数在自己的定义中使用自己,或者通过调用其它函数导致间接地对自身的使用,称为函数的递归定义。递归是LISP语言的基础。
迭代的意思就是重复。迭代和递归有所区别。迭代是某一计算过程的重复循环,而递归是在计算过程中要反复调用过程自身。
下载CLISP编译环境,并了解此系统的特点
经过长时间的上网搜索,下载到了CLISP
编译环境的2.33-2版本,通过测试,能运行一些基本的函数,具体能不能编译通过QSIM算法的LISP代码还需要进一步验证。不行的话只能再找其他版本的编译器。
具体运行过程也很简单,先打开命令行对话框,再在lisp.exe所在目录下输入:
...\lisp.exe –M lispinit.mem
即可进入Clisp界面,在此系统中不区分字母的大小写,且与其他lisp系统有个别出入,现已发现的有:加减乘除以及求反等各函数不再用ADD,DIFFERENCE,TIMES,MINUS等,而直接用符号“+,-,*,/”代替,此系统可用的其他数值计算函数还有floor,ceiling,mod,sin,cos,tan,sqrt,exp,expt等,且在此clisp系统中,比较函数LT,LE,GT,GE,ONEP,EQN等均被符号,>=等代替,可以使用的比较函数经上机证实的有:ZEROP,NUMBERP,MINUSP,EQ,MEMBER等,但是对于MEMBER函数,我们发现当参数S1属于参数S2的最外层元素时返回的不是T,而是整个S2参数。这一点和以前的系统略有不同。
还有,在对原子的性质表进行操作时,GET
和REMPROP函数均有效,但是PUTPROP函数无效,现在还不清楚此系统使用什么函数加入新的性质表项。
对于LET函数,他的作用和SETQ函数的不同之处在于LET函数只是暂时绑定,即原来存放SYMBOL的值的存储区域并没有被覆盖,可能只是将指针暂时指向一个临时存储区。
这种情况如:(IF 4 5 6),结果是5,这说明此系统默认所有非NIL值为真。除T和NIL外的自赋值符号变量可通过在符号串前加冒号(COLON)实现。
我们证实该系统支持科学计数法和复数表示,如:1.722e-15,#c(1.722e-15
0.75)该系统还支持自动类型转换,即当两个不同类型的数相加(减)时,遵循如下原则:整数+有理数->有理数;有理数+实数->实数;实数+复数->复数。除了机器的内存大小外,整数的最大绝对值没有其他限制。
一个空表可看成是一个空栈,可用PUSH和POP对其进行操作,为了更清楚地说明,举例如下:
> (setq a
nil)
NIL
> (push 4
a)
(4)
> (push 5
a)
(5 4)
> (pop
a)
5
> a
(4)
> (pop
a)
4
> (pop
a)
NIL
> a
NIL
在此系统中并不是用参考资料中所说的DEFINE定义函数,而是用DEFUN,可能是因为这样用更能明确定义的是一个函数而不是变量或其他什么东西。
此LISP系统还提供了&optional关键字,所有此关键字后的参数均为可选的,可选参数可用类似(x
3)的形式赋缺省值,若无明确定义则系统默认其缺省值为NIL,如:
>(defun baaz
(&optional (x 3) (z 10)) (+ x z))
BAAZ
>(baaz
5)
15
>(baaz 5
6)
11
>(baaz)
13
该系统还提供了&rest关键字用此关键字后的变量代表所有多出来的参数所组成的一个LIST。如:
>(defun foo (x
&rest y) y)
FOO
>(foo 4 5
6)
(5 6)