Author: ctian Date: Tue Aug 28 09:06:47 2007 New Revision: 25
Modified: books/onlisp/1-the_extensible_language.tex books/onlisp/2-functions.tex books/onlisp/onlisp.tex Log: begin to section 2.3
Modified: books/onlisp/1-the_extensible_language.tex ============================================================================== --- books/onlisp/1-the_extensible_language.tex (original) +++ books/onlisp/1-the_extensible_language.tex Tue Aug 28 09:06:47 2007 @@ -151,22 +151,18 @@ 如果你想要一个新变种的 \texttt{mapcar}, 那你就可以自己定义然后就像使用 \texttt{mapcar} 那样来使用它. 例如, 如果你想要某个函数当把从 1 到 10 的所有整数分别传给它以后得到的值的一个列表, 你可以创建一个新列表然后把它传给 \texttt{mapcar}: - \begin{verbatim} (mapcar fn (do* ((x 1 (1+ x)) (result (list x) (push x result))) ((= x 10) (nreverse result)))) \end{verbatim} - 但这种手法既不美观又缺乏效率.\footnote{你也可以使用 Common Lisp 的 series 宏把代码写得更简洁, 但那也只能证明同样观点, 因为这些宏就是 Lisp 本身的扩展} 反过来你也可以定义一个新的映射函数 \texttt{map1-n} (见某页), 然后用如下方式调用它: - \begin{verbatim} (map1-n fn 10) \end{verbatim} - 定义函数相对来说比较清楚明白. 宏提供了一种更通用, 但不太容易理解的定义新操作符的手段. 宏是用来写程序的程序. 这句话意味深长, 深入地探究这个问题正是本书的主要目的.
@@ -180,20 +176,16 @@ 如果需要花一些时间才能会用宏, 那它最好值得这样做. 即使像迭代这样平凡的用法中, 宏也可以使程序明显地变得小而清楚. 假设一个程序需要在某个程序体上从 a 到 b 来迭代 x. Lisp 内置的 \texttt{do} 用于更加一般的场合. 对于简单的迭代来说它并不能产生可读性最好的代码: - \begin{verbatim} (do ((x a (+ 1 x))) ((> x b)) (print x)) \end{verbatim} - 另一方面, 假如我们可以只写成这样: - \begin{verbatim} (for (x a b) (print x)) \end{verbatim} - 宏使这成为可能. 使用六行代码 (见某页) 我们就可以把 \texttt{for} 加入到语言中来, 就好像它一开始就在那里一样. 并且正如后面的章节所显示的那样, 写一个 \texttt{for} 对于我们可以用宏来做什么来说还只是个开始.
Modified: books/onlisp/2-functions.tex ============================================================================== --- books/onlisp/2-functions.tex (original) +++ books/onlisp/2-functions.tex Tue Aug 28 09:06:47 2007 @@ -1,7 +1,153 @@ \chapter{函数} \label{chap:functions}
-函数是 Lisp 程序的构造单元. 它们也是 Lisp 的构造单元. +函数是 Lisp 程序的构造单元. 它们也是 Lisp 的构造单元. 在多数语言里 \texttt{+} (加法) +操作符都和用户自定义的函数多少有些区别. 但 Lisp 采用了单一模型, 函数应用, 来描述一个程序所能完成的所有计算. +在 Lisp 里, \texttt{+} 是一个函数, 就好像那些你自己定义的函数一样. + +事实上, 除了很少量称为 \textsl{特殊形式 (special form)} 的操作符以外, 整个 Lisp 的核心就是函数的集合. +有什么可以阻止你给这个集合添加新函数呢? 答案是没有: 如果你认为某件事 Lisp 应该可以做, 那你就可以把它写出来, +然后你的新函数可以得到和内置函数同等的待遇. + +这一事实带给程序员重要的影响. 这意味着任何新函数可以被认为是对 Lisp 语言的扩充, 也可以是特定应用的一部分. +典型情况是, 一个有经验的 Lisp 程序员两边都写一些, 然后不断调整语言和应用之间的界限直到它们彼此完美地配合. +本书正是关于如何在语言和应用之间达到最佳匹配. 由于我们为了向这一最终目标迈进的每一步都依赖于函数, +所以自然应该从函数开始. + +\section{函数就是数据} +\label{sec:functions_as_data} + +两件事使 Lisp 函数与众不同. 一个是前面提到的, Lisp 本身就是函数的集合. 这意味着我们可以自己给 Lisp +增加新操作符. 关于函数的另一件重要的事情是要了解它们也是 Lisp 对象. + +Lisp 提供了其他语言里能找到的大多数数据类型. 我们有整数和浮点数, 字符串, 数组, 结构体等等. +但 Lisp 还支持一种初看起来感到惊奇的数据类型: 函数. 几乎所有编程语言都提供某种形式的函数或过程. +为什么说 Lisp 作为数据类型提供它呢? 这意味着在 Lisp 里我们可以像对待其他熟悉的数据类型那样来对待函数, +就像整数那样: 在运行期创建一个新整数, 把它们存在变量和结构体里面, 把它们作为参数传给其他函数, +以及让函数返回它们作为结果. + +这种在运行期创建和返回函数的能力特别有用. 这可能初看起来还有点半信半疑, +就好像那些可以在某些计算机上运行的可以修改自身的机器语言一样. 但对于 Lisp 来说, +在运行期创建新函数的技术简直就是家常便饭. + +\section{定义函数} +\label{sec:defining_functions} + +多数人首先学会用 \texttt{defun} 来创建函数. 下面的表达式定义了一个叫 \texttt{double} 的函数, +它能返回所给参数的两倍. +\begin{verbatim} +> (defun double (x) (* x 2)) +DOUBLE +\end{verbatim} +如果把上述定义送给 Lisp, 然后我们就可以从其他函数调用 \texttt{double}, 或者从最顶层(toplevel): +\begin{verbatim} +> (double 1) +2 +\end{verbatim} +一个 Lisp 代码文件通常主要由类似这样的函数定义组成, 有点儿类似 C 或者 Pascal 这类语言文件中的过程定义. +但接下来就有很多不同了. 这些 \texttt{defun} 并不只是过程定义, 它们也是 Lisp 调用. 后面我们考察 +\texttt{defun} 的背后发生了什么时, 这种区别就会看得更清楚. + +函数本身同时对象. \texttt{defun} 实际所做的就是构造一个这样的对象, 然后把他存放在第一个参数所指定的名下. +所以当我们调用 \texttt{double} 时, 我们得到的是这个名字所对应的那个函数对象. 得到这个对象的通常做法是使用 +\sq (井号--单引号) 操作符. 这个操作符可以理解成将名字映射到实际的函数对象. 通过将它附加到 +\texttt{double} 这个名字上: +\begin{verbatim} +> #'double +#<Interpreted Function DOUBLE {5811A899}> +\end{verbatim} +我们得到了通过上面的定义创建的实际对象. 尽管他的表示法对于不同的 Lisp 实现来说各不相同\footnote{ +译者注: 函数对象在交互式环境下的输出格式是 Lisp 厂商自行定义的. 本书中使用的是 CMUCL (CMU Common Lisp) 环境, 但提示符部分仍然保留了 \texttt{>}, 输出的样子看起来和原书有些不同, 但这无关紧要. 而且 CMUCL +的输出格式包含的信息更多一些 (但也没有 LispWorks 多)}, Common Lisp 函数是第一类 (first-class) 对象, +跟诸如整数和字符串这样的熟悉对象享有完全相同的权利. 所以, 我们可以把这个函数作为参数来传递, 返回这个函数, +把它存在数据结构里, 诸如此类: +\begin{verbatim} +> (eq #'double (car (list #'double))) +T +\end{verbatim} + +我们甚至可以不用 \texttt{defun} 来定义函数. 如同大多数 Lisp 对象那样, 我们可以原样引用它. +当我们想要引用一个整数时, 只要使用这个整数本身. 要表示一个字符串, 我们使用括在两个双引号之间的一系列字符. +要想表示一个函数, 我们可以使用一种称为 \textsl{\lexpr(lambda-expression)} 的东西. +一个 \lexpr 是一个由三部分组成的列表: \texttt{lambda} 符号, 参数列表, +以及包含零个以上表达式的主体. 下面这个 \lexpr 相当于一个和 \texttt{double} 等价的函数: +\begin{verbatim} +(lambda (x) (* x 2)) +\end{verbatim} +它描述了一个函数, 带有一个参数 $x$, 并且返回 $2x$. + +一个 \lexpr 也可以把它看作是一个函数的名字. 如果 \texttt{double} 是一个正确的名字, 比如说 +``米开朗基罗'', 那么 \texttt{(lambda (x) (* x 2))} 就相当于一个确切的描述, 例如 +``绘制希斯庭大教堂顶棚壁画的那个人''. 通过把一个井号--引号放在 \lexpr 的前面, 我们就得到了相应的函数: +\begin{verbatim} +> #'(lambda (x) (* x 2)) +#<Interpreted Function (LAMBDA (X) (* X 2)) {5812FFB1}> +\end{verbatim} +这个函数和 \texttt{double} 的表现相同, 但它们是两个不同的对象. + +在函数调用中, 函数名出现在前面, 然后是参数: +\begin{verbatim} +> (double 3) +6 +\end{verbatim} +由于 \lexpr 同时也是函数的名字, 它们也可以出现在函数调用的开始: +\begin{verbatim} +> ((lambda (x) (* x 2)) 3) +6 +\end{verbatim} + +在 Common Lisp 里, 我们可以同时拥有以 \texttt{double} 命名的函数和变量. +\begin{verbatim} +> (setq double 2) +2 +> (double double) +4 +\end{verbatim} +当一个名字出现在函数调用的前头, 或者前置一个 \sq 的时候, 它用来引用一个函数. 其他场合它被当作一个变量名. + +因此我们说 Common Lisp 拥有分开的函数和变量 \textsl{命名空间 (name-space)}. 我们可以同时有一个叫 +\texttt{foo} 变量以及一个叫 \texttt{foo} 的函数, 且它们不必相同. 这种情形可能令人困惑, +并且可能导致代码一定程度上的丑陋, 但这是 Common Lisp 程序员必须面对的东西.\footnote{ +译者注: 拥有分开的变量和函数命名空间的 Lisp 称为 Lisp--2, 在另一类 Lisp--1 下, +变量和函数定义在同一个命名空间里, 最著名的这种 Lisp 方言是 Scheme. 关于 Lisp--1 VS Lisp--2 +的讨论在网上有很多, 一般观点认为 Lisp--1 对于编译器来说更难实现.} + +如果需要的话, Common Lisp 还提供了两个函数用于将符号映射到它所代表的函数或者变量. \texttt{symbol-value} +函数以一个符号为参数, 返回其对应变量的值: +\begin{verbatim} +> (symbol-value 'double) +2 +\end{verbatim} +而 \texttt{symbol-function} 则用来得到一个全局定义的函数: +\begin{verbatim} +> (symbol-function 'double) +#<Interpreted Function DOUBLE {5811A899}> +\end{verbatim} +注意到, 由于函数也是普通的对象, 所以变量也把函数作为它的值: +\begin{verbatim} +> (setq x #'append) +#<Function APPEND {10064D71}> +> (eq (symbol-value 'x) (symbol-function 'append)) +T +\end{verbatim} + +深入分析的话, \texttt{defun} 实际上是把它第一个参数的 \texttt{symbol-function} +设置成用它其余部分构造的函数. 下面两个表达式基本上做了同一件事: +\begin{verbatim} +(defun double (x) (* x 2)) + +(setf (symbol-function 'double) + #'(lambda (x) (* x 2))) +\end{verbatim} +所以 \texttt{defun} 和其他语言的过程定义具有相同的效果---把一个名字和一段代码关联起来. +但是底层手法完全不同. 我们不需要用 \texttt{defun} 来创建函数, 函数也不是一定要保存在一些符号的值里. +如同其他语言里的过程定义的 \texttt{defun}, 它的潜在特征其实是个更一般的手法: +构造一个函数和把它跟一个确定的名字关联起来, 其实是两个分开的操作. +当我们不需要用到函数的这种完全一般化的 Lisp 观念时, 用 \texttt{defun} +来生成函数就和在那些限制更多的语言里一样的简单. + +\section{函数型参数} +\label{sec:functional-arguments}
%%% Local Variables: %%% coding: utf-8
Modified: books/onlisp/onlisp.tex ============================================================================== --- books/onlisp/onlisp.tex (original) +++ books/onlisp/onlisp.tex Tue Aug 28 09:06:47 2007 @@ -1,11 +1,15 @@ \documentclass[a4paper,10pt]{book} \usepackage{CJK} \usepackage{indentfirst} +\usepackage{amsmath}
\begin{document} \begin{CJK}{UTF8}{song} \CJKindent
+\newcommand{\lexpr}{$\lambda$--表达式} +\newcommand{\sq}{\texttt{#'}} + \begin{titlepage} \title{On Lisp\footnote{原书地址: \texttt{http://www.paulgraham.com/onlisp.html%7D%7D%7D \author{Paul Graham 著
cl-net-snmp-cvs@common-lisp.net