其君天下也,炎之如日,威之如神,函之如海,养之如春
——《汉书·叙传》
上一集我们讲到了“函数”,其实这个概念早在初中数学里就已经学过了,一个函数无非就是将自变量映射到值的对应关系,在Lisp里也一样。
Lisp中的函数定义我们已经在上节给出(快速抢答:谁还记得请举手),在Lisp中采用如下形式描述一个函数:
(lambda (p1 p2 ... pn) e)
其中,pi为原子,在函数中称之为参数,e是表达式,也就是函数体。
调用一个函数的方式如下:
((lambda (p1 p2 ... pn) e) a1 a2 ... an)
其中ai为表达式,按照我们的惯例,称之为实参。
整个函数的调用过程如下:每一个表达式ai(实参)先求值,然后再将这些实参代入e中求值,最后的结果即为整个表达式的返回值。
如果一个表达式的第一个元素是一个原子,但不是基本操作符(也就是我们先前提到的那7个),如:
(f a1 a2 ... an)
并且f的值是一个函数(lambda (p1 p2 ... pn) e),则上述表达式等价于
((lambda (p1 p2 ... pn) e) a1 a2 ... an)
看了这一段,可能大家都有点晕,到窗口去做几个深呼吸,然后回来做下面这个练习,看看这个表达式的结果是什么?
((lambda (f) (f '(b c))) '(lambda (x) (cons 'a x)))
如果你得出了结果,那么继续往下看,否则再把前面几段话多读几遍,把上面的练习输入到一个能自动匹配括号的文本编辑器里继续研究。
在这里我打算插几句题外话,可能有很多人已经见识过这个lambda了,不过不太可能是在Lisp里(要是这样的话你就应该不用来看这片“入门”了,不是吗?),而多半是在Python里,Python手册中对这个lambda仅仅是一笔带过,他大概是这么说的:“使用lambda这个词是因为它类似于Lisp语言里同名的一个语法结构。”好了,我们现在就来看看lambda这个典故的真正起源。
lambda这个词来源于lambda演算理论。lambda是什么?对于现在的人来说,这个概念不过就是“函数”而已,但是对于lambda演算理论的出现的那个年代来说,它可是一种革命性的创新。lambda演算理论过于复杂,而且作为一篇Lisp的简介,讨论它已经完全偏离了主题,但是它所提出的另一个概念——高阶函数(High Order Function)——却在Lisp语言中占有重要的地位,甚至可以说是Lisp如此与众不同的主要原因。
正如“高阶导数”就是“导数的导数”一样,所谓高阶函数,其实就是“函数的函数”(高数老师,原谅我吧)。即把一个函数本身当作另一个函数的自变量(在现代的C++中提出的“functor”这个概念其实就是高阶函数在C++中的一种实现)。高阶函数的出现,将“函数”在编程语言中的地位提升到一个“一等公民”的地位,你可以像操作任何基本数据类型一样操作一个函数,对它进行变换、传递,随你怎么折腾。
下面我们回到正题,继续讨论Lisp中的函数,我们可以看到,至今为止,我们的函数都还没有名字,函数可以没有名字,也就是匿名函数正是Lisp的另一大特色,Lisp可以让程序员把数据和名字剥离开,这对于许多其它的编程语言来说是直到现在也无法享受到的一种奢侈。
函数没有名字会带来一个问题,那就是你无法在函数中调用自身(好啦,我知道还有Y组合,不过这是一篇入门文章),所以Lisp提供了一种形式可以让你用一个标识符来引用函数:
(label f (lambda (p1 p2 ... pn) e))
这个表达式和前面的简单lambda表达式等价,但是在e中出现的所有f都会被替换为整个lambda表达式,也就是递归。
同时,Lisp为它提供了一种简写形式:
(defun f (p1 p2 ... pn) e)
你可以开始写你的第一个有用的Lisp程序了,你打算写什么?(无论什么,只要不是Hello world就好)
下一集,我们将给出一些常用的函数。
广告之后我们再回来…………
注:这一集要讲的东西太多,可惜我驾驭长篇的能力实在太差,不免写的有些前言不搭后语,如果你对此有什么意见和建议就直接提吧,我会改的。