- UID
- 10108
- 积分
- 5956
- 精华
- 贡献
-
- 威望
-
- 活跃度
-
- D豆
-
- 在线时间
- 小时
- 注册时间
- 2002-9-17
- 最后登录
- 1970-1-1
|
楼主 |
发表于 2013-12-10 01:22:00
|
显示全部楼层
你好,Lisp
到此刻为止,我们所知的关于Lisp的指示可以总结为一句话:Lisp是一个可执行的语法更优美的XML,但我们还没有说Lisp是怎样做到这一点的,现在开始补上这个话题。
Lisp有丰富的内置数据类型,其中的整数和字符串和其他语言没什么分别。像71或者”hello”这样的值,含义也和C++或者Java这样的语言大体相同。真正有意思的三种类型是符号(symbol),表和函数。这一章的剩余部分,我都会用来介绍这几种类型,还要介绍Lisp环境是怎样编译和运行源码的。这个过程用Lisp的术语来说通常叫做求值。通读这一节内容,对于透彻理解元编程的真正潜力,以及代码和数据的同一性,和面向领域语言的观念,都极其重要。万勿等闲视之。我会尽量讲得生动有趣一些,也希望你能获得一些启发。那好,我们先讲符号。
大体上,符号相当于C++或Java语言中的标志符,它的名字可以用来访问变量值(例如currentTime,arrayCount,n,等等),差别在于,Lisp中的符号更加基本。在C++或Java里面,变量名只能用字母和下划线的组合,而Lisp的符号则非常有包容性,比如,加号(+)就是一个合法的符号,其他的像-,=,hello-world,*等等都可以是符号名。符号名的命名规则可以在网上查到。你可以给这些符号任意赋值,我们这里先用伪码来说明这一点。假定函数set是给变量赋值(就像等号=在C++和Java里的作用),下面是我们的例子:
- set(test, 5) // 符号test的值为5
- set(=, 5) // 符号=的值为5
- set(test, "hello") // 符号test的值为字符串"hello"
- set(test, =) // 此时符号=的值为5, 所以test的也为5
- set(*, "hello") // 符号*的值为"hello"
好像有什么不对的地方?假定我们对*赋给整数或者字符串值,那做乘法时怎么办?不管怎么说,*总是乘法呀?答案简单极了。Lisp中函数的角色十分特殊,函数也是一种数据类型,就像整数和字符串一样,因此可以把它赋值给符号。乘法函数Lisp的内置函数,默认赋给*,你可以把其他函数赋值给*,那样*就不代表乘法了。你也可以把这函数的值存到另外的变量里。我们再用伪码来说明一下:
- *(3,4) // 3乘4, 结果是12
- set(temp, *) // 把*的值, 也就是乘法函数, 赋值给temp
- set(*, 3) // 把3赋予*
- *(3,4) // 错误的表达式, *不再是乘法, 而是数值3
- temp(3,4) // temp是乘法函数, 所以此表达式的值为3乘4等于12
- set(*, temp) // 再次把乘法函数赋予*
- *(3,4) // 3乘4等于12
再古怪一点,把减号的值赋给加号:
- set(+, -) // 减号(-)是内置的减法函数
- +(5, 4) // 加号(+)现在是代表减法函数, 结果是5减4等于1
这只是举例子,我还没有详细讲函数。Lisp中的函数是一种数据类型,和整数,字符串,符号等等一样。一个函数并不必然有一个名字,这和C++或者Java语言的情形很不相同。在这里函数自己代表自己。事实上它是一个指向代码块的指针,附带有一些其他信息(例如一组参数变量)。只有在把函数赋予其他符号时,它才具有了名字,就像把一个数值或字符串赋予变量一样的道理。你可以用一个内置的专门用于创建函数的函数来创建函数,然后把它赋值给符号fn,用伪码来表示就是:
这段代码返回一个具有一个参数的函数,函数的功能是计算参数乘2的结果。这个函数还没有名字,你可以把此函数赋值给别的符号:
set(times-two, fn [a] {return *(a, 2)}) 我们现在可以这样调用这个函数:
time-two(5) // 返回10 我们先跳过符号和函数,讲一讲表。什么是表?你也许已经听过好多相关的说法。表,一言以蔽之,就是把类似XML那样的数据块,用s表达式来表示。表用一对括号括住,表中元素以空格分隔,表可以嵌套。例如(这回我们用真正的Lisp语法,注意用分号表示注释):
- () ; 空表
- (1) ; 含一个元素的表
- (1 "test") ; 两元素表, 一个元素是整数1, 另一个是字符串
- (test "hello") ; 两元素表, 一个元素是符号, 另一个是字符串
- (test (1 2) "hello") ; 三元素表, 一个符号test, 一个含有两个元素1和2的
- ; 表, 最后一个元素是字符串
当Lisp系统遇到这样的表时,它所做的,和Ant处理XML数据所做的,非常相似,那就是试图执行它们。其实,Lisp源码就是特定的一种表,好比Ant源码是一种特定的XML一样。Lisp执行表的顺序是这样的,表的第一个元素当作函数,其他元素当作函数的参数。如果其中某个参数也是表,那就按照同样的原则对这个表求值,结果再传递给最初的函数作为参数。这就是基本原则。我们看一下真正的代码:
- (* 3 4) ; 相当于前面列举过的伪码*(3,4), 即计算3乘4
- (times-two 5) ; 返回10, times-two按照前面的定义是求参数的2倍
- (3 4) ; 错误, 3不是函数
- (time-two) ; 错误, times-two要求一个参数
- (times-two 3 4) ; 错误, times-two只要求一个参数
- (set + -) ; 把减法函数赋予符号+
- (+ 5 4) ; 依据上一句的结果, 此时+表示减法, 所以返回1
- (* 3 (+ 2 2)) ; 2+2的结果是4, 再乘3, 结果是12
上述的例子中,所有的表都是当作代码来处理的。怎样把表当作数据来处理呢?同样的,设想一下,Ant是把XML数据当作自己的参数。在Lisp中,我们给表加一个前缀’来表示数据。
- (set test '(1 2)) ; test的值为两元素表
- (set test (1 2)) ; 错误, 1不是函数
- (set test '(* 3 4)) ; test的值是三元素表, 三个元素分别是*, 3, 4
我们可以用一个内置的函数head来返回表的第一个元素,tail函数来返回剩余元素组成的表。
- (head '(* 3 4)) ; 返回符号*
- (tail '(* 3 4)) ; 返回表(3 4)
- (head (tal '(* 3 4))) ; 返回3
- (head test) ; 返回*
你可以把Lisp的内置函数想像成Ant的任务。差别在于,我们不用在另外的语言中扩展Lisp(虽然完全可以做得到),我们可以用Lisp自己来扩展自己,就像上面举的times-two函数的例子。Lisp的内置函数集十分精简,只包含了十分必要的部分。剩下的函数都是作为标准库来实现的。 |
|