Author: ctian Date: Tue Aug 28 12:50:50 2007 New Revision: 27
Modified: books/onlisp/2-functions.tex Log: more text
Modified: books/onlisp/2-functions.tex ============================================================================== --- books/onlisp/2-functions.tex (original) +++ books/onlisp/2-functions.tex Tue Aug 28 12:50:50 2007 @@ -146,9 +146,136 @@ 当我们不需要用到函数的这种完全一般化的 Lisp 观念时, 用 \texttt{defun} 来生成函数就和在那些限制更多的语言里一样的简单.
-\section{函数型参数} +\section{函数作为参数} \label{sec:functional-arguments}
+作为数据对象的函数意味着, 像其他东西那样, 我们可以把它传递给其他函数. 这种可能性对于 Lisp +的自下而上编程负有重要责任. + +一个将函数视为数据对象的语言必然提供某种方式去调用它们. 在 Lisp 里, 这个函数是 \texttt{apply}. 一般而言, +我们用两个参数来调用 \texttt{apply}: 一个函数和它的参数列表. 下面的四个表达式具有相同效果: +\begin{verbatim} +(+ 1 2) + +(apply #'+ '(1 2)) + +(apply (symbol-function '+) '(1 2)) + +(apply #'(lambda (x y) (+ x y)) '(1 2)) +\end{verbatim} +在 Common Lisp 里, \texttt{apply} 可以带有任意数量的参数, 首先给出的函数, 将被应用到通过将其余参数 cons +到最后一个列表里得到的那个列表上. 所以表达式 +\begin{verbatim} +(apply #'+ 1 '(2)) +\end{verbatim} +等价于前面那四个. 如果不方便以列表形式提供参数, 我们可以使用 \texttt{funcall}, 它和 \texttt{apply} +唯一的区别就在这里. 表达式 +\begin{verbatim} +(funcall #'+ 1 2) +\end{verbatim} +和上面那些是一样的效果. + +很多内置的 Common Lisp 函数可以带函数型参数. 其中最广泛使用的是映射函数. 例如 \texttt{mapcar} +带有两个以上参数, 一个函数加上一个以上的列表 (每个列表都分别是函数的参数), +然后它可以将参数里的函数依次作用在每个列表的元素上: +\begin{verbatim} +> (mapcar #'(lambda (x) (+ x 10)) + '(1 2 3)) +(11 12 13) +> (mapcar #'+ + '(1 2 3) + '(10 100 1000)) +(11 102 1003) +\end{verbatim} +Lisp 程序经常需要对一个列表中的每一项都做一些操作然后再把结果以列表形式返回. +上述的第一个例子阐述了做这件事的便捷方式: 生成一个做你想做的事情的函数, 然后把它映射到列表上. + +我们已经看到了把函数当作数据对待带来了多大的便利. 在许多语言里, 即使我们可以像 \texttt{mapcar} +那样把函数作为参数传递进去, 那也只能是在先前的代码文件中定义好了的函数. +如果只有一小段代码需要把列表中的每一项都加上 10, 我们就不得不定义一个函数, 叫做 \texttt{plus_ten} +或者类似的东西, 然后就只能用这一次. 有了 \lexpr, 我们就可以直接引用函数. + +Common Lisp 和在它之前的方言的一个最大区别就是它包含有大量使用函数型参数的内置函数. 其中两个最常用的, +除了随处可见的 \texttt{mapcar}, 就是 \texttt{sort} 和 \texttt{remove-if} 了. 前者是通用的排序函数. +他接受一个列表和一个谓词, 然后返回一张通过将成对元素传到谓词里得到的排序表. +\begin{verbatim} +> (sort '(1 4 2 5 6 7 3) #'<) +(1 2 3 4 5 6 7) +\end{verbatim} +记住 \texttt{sort} 函数工作方式的一种方法是, 如果你用 $<$ 排序一个没有重复元素的列表, 那么当你把 $<$ +应用到结果列表的时候, 它将会返回真. + +如果 \texttt{remove-if} 函数不包含在 Common Lisp 中的话, 那它就应该是你写的第一个工具. 它接受一个函数和列表, +并且返回这个列表中所有调用那个函数返回假的元素. +\begin{verbatim} +> (remove-if #'evenp '(1 2 3 4 5 6 7)) +(1 3 5 7) +\end{verbatim} + +作为一个接受函数作为参数的函数示例, 这里给出一个 \texttt{remove-if} 的受限版本: +\begin{verbatim} +(defun our-remove-if (fn lst) + (if (null lst) + nil + (if (funcall fn (car lst)) + (our-remove-if fn (cdr lst)) + (cons (car lst) (out-remove-if fn (cdr lst)))))) +\end{verbatim} +注意到在这个定义里 \texttt{fn} 并没有前缀 \sq. 因为函数就是数据对象, 变量可以将一个函数作为它的正规值. +这就是此处发生的事情. \sq 仅用来引用那些以符号命名的函数---通常是用 \texttt{defun} 全局定义的. + +正如第 4 章将要显示的那样, 编写那种接受函数作为参数的新工具是自下而上程序设计的重要环节. Common Lisp +自带了非常多的工具函数, 很多你想要的可能已经存在了. 但无论是使用内置的 \texttt{sort} 还是你自己写的工具, +原则上都一样. Instead of wiring in functionality, pass a functional argument. + +\section{函数作为属性} +\label{sec:functions_as_properties} + +函数作为 Lisp 对象这一事实也允许我们能够编写出那种可以随时扩展以应付新需求的程序. +假设我们需要写一个以动物类型为参数然后产生相应行为的函数. 在大多数语言里, 做到这点的方式是使用一个 \texttt{case} +语句, 我们也可以在 Lisp 里用这种方式: +\begin{verbatim} +(defun behave (animal) + (case animal + (dog (wag-tail) + (bark)) + (rat (scurry) + (squeak)) + (cat (rub-legs) + (scratch-carpet)))) +\end{verbatim} + +如果我们增加一种新动物会发生什么事? 如果我们正计划增加新的动物, 那么把 \texttt{behave} +定义成下面的样子可能更好一些: +\begin{verbatim} +(defun behave (animal) + (funcall (get animal 'behavior))) +\end{verbatim} +然后把每种个体动物的行为以单独的函数形式保存, 例如存放在以它们名字命名的属性列表里: +\begin{verbatim} +(setf (get 'dog 'behavior) + #'(lambda () + (wag-tail) + (bark))) +\end{verbatim} +以这种方式, 增加一种新动物你需要做的全部事情就是定义一个新的属性. 不需要重写任何函数. + +上述第二种观点, 尽管更为灵活, 但是看起来更慢一些. 确实是这样. 如果速度的关键问题, +我们可以使用结构体来代替属性表, 并且尤其要用编译过的函数代替解释性的函数. (第 2.9 章解释了怎样做到这些.) +有了结构体和编译函数, 上述更具灵活性的代码就可以达到甚至超过使用 \texttt{case} 语句那个版本的速度. + +函数的这种用法相当于面向对象编程中的 \textsl{方法} 概念. 通常来讲, 方法是作为对象一种属性的函数, +也就是我们已有的那些. 如果你把继承引入这个模型, 你就得到了面向对象编程的全部要素. +第 25 章将用令人惊奇的极少代码来说明这一点. + +面向对象编程的一个大卖点是它可以使程序可扩展. 这种前景在 Lisp 界并未引起重视, 因为这里从来都是可扩展的. +如果我们需要的可扩展性对于继承的需求不是很多, 那可能只需纯 Lisp 就够了. + +\section{作用域} +\label{sec:scope} + +Common Lisp 是一种词法作用域 (lexically scope) 的 Lisp. Scheme 是最早的词法作用域方言; 在 Scheme 之前 + %%% Local Variables: %%% coding: utf-8 %%% mode: latex
cl-net-snmp-cvs@common-lisp.net