cl-net-snmp-cvs
Threads by month
- ----- 2025 -----
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
August 2007
- 1 participants
- 7 discussions
Author: ctian
Date: Thu Aug 30 09:34:36 2007
New Revision: 30
Added:
books/onlisp/7-macros.tex
Modified:
books/onlisp/2-functions.tex
books/onlisp/3-functional_programming.tex
books/onlisp/onlisp.tex
Log:
finish chap 2, start chap 3
Modified: books/onlisp/2-functions.tex
==============================================================================
--- books/onlisp/2-functions.tex (original)
+++ books/onlisp/2-functions.tex Thu Aug 30 09:34:36 2007
@@ -352,7 +352,7 @@
(defun make-adder (n)
#'(lambda (x) (+ x n)))
\end{verbatim}
-接受一个数值参数, 然后返回一个闭包, 当后者被调用时, 能够把之前那个数加到它的参数上.
+\label{fun:make-adder} 接受一个数值参数, 然后返回一个闭包, 当后者被调用时, 能够把之前那个数加到它的参数上.
我们可以根据需要生成任意数量的这种加法器:
\begin{verbatim}
> (setq add2 (make-adder 2)
@@ -514,6 +514,168 @@
\section{尾递归}
\label{sec:tail-recursion}
+递归函数调用它们自身. 如果调用以后没其他事可做, 这种调用就称为\textsl{尾递归 (tail-recursive)}.
+下面这个函数不是尾递归的
+\begin{verbatim}
+(defun our-length (lst)
+ (if (null lst)
+ 0
+ (1+ (out-length (cdr lst)))))
+\end{verbatim}
+因为在从递归调用返回之后我们又把结果传给了 \texttt{1+}. 而下面这个函数就是尾递归的
+\begin{verbatim}
+(defun out-find-if (fn lst)
+ (if (funcall fn (car lst))
+ (car lst)
+ (our-find-if fn (cdr lst))))
+\end{verbatim}
+因为通过递归调用得到的值被立即返回了.
+
+尾递归是非常理想的, 因为许多 Common Lisp 编译器都可以把尾递归转化成循环. 在这种编译器下,
+你可以在源代码里书写优雅的递归而不必担心它们在运行期导致函数调用过载.
+
+一个不是尾递归的函数经常可以通过嵌入一个称为 \textsl{聚集器} 的本地函数, 来转换成尾递归的形式.
+在当前上下文里, 聚集器仅指一个代表当前已完成计算量的值. 例如 \texttt{our-length} 可以转换成
+\begin{verbatim}
+(defun our-length (lst)
+ (labels ((rec (lst acc)
+ (if (null lst)
+ acc
+ (rec (cdr lst) (1+ acc)))))
+ (rec lst 0)))
+\end{verbatim}
+在这里, 当前所见的列表元素的数量包含在另一个参数 \texttt{acc} 里面了. 当递归到达列表的结尾, \texttt{acc}
+的值就是总的长度, 只要直接返回就好. 通过在向下调用树的过程中聚集一个值, 而不是在返回的时候构造它, 我们就可以将
+\texttt{rec} 尾递归化.
+
+许多 Common Lisp 编译器可以做尾递归优化, 但并非所有都是默认行为. 所以在你编写尾递归函数时, 你也许需要将
+\begin{verbatim}
+(proclaim '(optimize speed))
+\end{verbatim}
+写在文件的最前面, 确保编译器可以利用你的优化努力.
+
+提供尾递归和类型声明, 现存的 Common Lisp 编译器都可以生成运行起来快得多的代码, 甚至快过 C. Richard Gabriel
+给出下列函数作为示例, 它可以从 1 加到 \texttt{n}:
+\begin{verbatim}
+(defun triangle (n)
+ (labels ((tri (c n)
+ (declare (type fixnum n c))
+ (if (zerop n)
+ c
+ (tri (the fixnum (+ n c))
+ (the fixnum (- n 1))))))
+ (tri 0 n)))
+\end{verbatim}
+这就是快速的 Common Lisp 代码看起来的样子. 一开始以这种方式写程序可能不太自然.
+通常的方式是首先以尽可能自然的方式来写一个函数, 然后如果必要的话, 把它转化成等价的尾递归形式.
+
+\section{编译}
+\label{sec:compilation}
+
+Lisp 函数可以单独编译或者按文件编译. 如果你只是在 toplevel 下输入一个 \texttt{defun} 表达式.
+\begin{verbatim}
+> (defun foo (x) (1+ x))
+FOO
+\end{verbatim}
+多数实现会创建一个解释函数. 你可以使用 \texttt{compiled-function-p} 来检查一个函数是否已被编译:
+\begin{verbatim}
+> (compiled-function-p #'foo)
+NIL
+\end{verbatim}
+我们可以通过把通过把名字送给 \texttt{compile} 来编译 \texttt{foo}
+\begin{verbatim}
+> (compile 'foo)
+FOO
+\end{verbatim}
+这就可以编译 \texttt{foo} 的定义并且把之前的解释版本替换成编译版本.
+\begin{verbatim}
+> (compiled-function-p #'foo)
+T
+\end{verbatim}
+编译和解释函数都是 Lisp 对象, 它们的对外表现相同, 除了 \texttt{compiled-function-p} 这部分.
+直接给出的函数也可以编译: \texttt{compile} 希望它的第一个参数是一个名字, 但如果你给它一个 \texttt{nil},
+它就会编译第二个参数给出的 \lexpr.
+\begin{verbatim}
+> (compile nil '(lambda (x) (+ x 2)))
+#<Function "LAMBDA (X)" {58128F31}>
+\end{verbatim}
+如果你同时给出名字和函数参数, \texttt{compile} 就相当于编译一个 \texttt{defun} 了:
+\begin{verbatim}
+> (progn (compile 'bar '(lambda (x) (* x 3)))
+ (compiled-function-p #'bar))
+T
+\end{verbatim}
+
+把 \texttt{compile} 集成进语言里意味着一个程序可以随时构造和编译新函数. 尽管如此, 显式调用 \texttt{compile}
+仍属于过激手段, 和调用 \texttt{eval} 相比同样值得怀疑.\footnote{某页有关于为何说显式调用 \texttt{eval}
+不好的一个解释.} 当第 \ref{sec:functions_as_data} 章里说在运行时创建新函数属常用编程技术时, 它指的是从类似
+\texttt{make-adder} 那样的函数中生成的闭包, 并非指从原始列表里调用 \texttt{compile} 得到的新函数. 调用
+\texttt{compile} 并非常用编程技术---相反是极其罕见的. 所以除非必要就别那样做. 就算你在实现 Lisp
+之上实现另一种语言 (并且甚至已经花费大量时间), 你需要的可能也只是宏.
+
+有两类函数你不能作为参数送给 \texttt{compile}. 根据 \textsc{CLTL2} (第 667 页), 你不能编译一个
+``解释性地定义在一个非空词法环境中的'' 函数. 那就是说, 在 toplevel 下你定义一个带有 \texttt{let} 的
+\texttt{foo}
+\begin{verbatim}
+> (let ((y 2))
+ (defun foo (x) (+ x y)))
+\end{verbatim}
+然后 \texttt{(compile 'foo)} 并不一定能正常工作.\footnote{把这段代码写在文件里然后再编译是好的.
+这一限制是由于具体实现的原因强加在解释型函数上的, 绝不是因为在如此清晰的词法环境中定义函数有什么错误.}
+你也不能在一个已经编译了的函数上调用 \texttt{compile}, \textsc{CLTL2} 隐晦地暗示 ``结果是不确定的''.
+
+通常编译 Lisp 代码的方式是并非使用 \texttt{compile} 逐个函数地编译, 而是使用 \texttt{compile-file}
+编译整个文件. 该函数接受一个文件名然后创建源代码的一个编译版本---典型的是带有相同基本文件名但有不同扩展名.
+当被编译过的文件加载时, 文件里定义的所有函数对于 \texttt{compiled-function-p} 来说都是真.
+
+以后的章节还依赖于编译带来的另一种效果: 当一个函数产生于另一个函数里, 并且外网函数已经编译,
+那么里面的函数将也会被编译. \textsc{CLTL2} 里并没有明确这一行为, 但所有正规的实现都支持.
+
+对于那些返回函数的函数来说, 内层函数的编译行为是很清楚的. 当 \texttt{make-adder}
+(第 \pageref{fun:make-adder} 页) 被编译时, 它将返回一个编译了的函数:
+\begin{verbatim}
+> (compile 'make-adder)
+MAKE-ADDER
+> (compiled-function-p (make-adder 2))
+T
+\end{verbatim}
+后面的章节里将说明, 这一事实对于实现嵌入式语言来说尤为重要. 如果一种新语言是通过转换来实现的,
+并且转换出来的代码是编译了的, 那它也会产生编译了的输出---也就变成了新语言的高效的编译器.
+(一个简单的例子描述在某页上)
+
+如果我们有一个特别小的函数, 我们可能要求它被编译成内联的. 否则调用这个函数的开销可能比这个函数本身的开销都大.
+如果我们定义了一个函数:
+\begin{verbatim}
+(defun 50th (lst) (nth 49 lst))
+\end{verbatim}
+并且做了声明:
+\begin{verbatim}
+(proclaim '(inline 50th))
+\end{verbatim}
+然后一个编译的函数里针对 \texttt{50th} 的引用就不需要一个实际的函数调用了. 如果我们定义并且编译一个调用了
+\texttt{50th} 的函数,
+\begin{verbatim}
+(defun foo (lst)
+ (+ (50th lst) 1))
+\end{verbatim}
+那么当 \texttt{foo} 被编译时, \texttt{50th} 的代码应该被编译进它里面. 就好像我们一开始写的就是
+\begin{verbatim}
+(defun foo (lst)
+ (+ (nth 49 lst) 1))
+\end{verbatim}
+那样. 唯一的缺点是当我们重新定义 \texttt{50th} 以后, 也必须重新编译 \texttt{foo}, 否则它用的还是旧定义.
+内联函数的限制基本上和宏差不多 (见第 \ref{sec:dependence_on_macros} 章).
+
+\section{作为列表的函数}
+\label{functions_from_lists}
+
+在一些早期的 Lisp 方言中, 函数被表达成一个列表. 这给了程序员们不同寻常的编写和执行他们自己的 Lisp 程序的能力.
+在 Common Lisp 中, 函数不再由列表组成---好的实现把它们编译成本机代码. 但你仍然可以写出那种写程序的程序,
+因为列表是编译器的输入.
+
+难以形容 Lisp 程序可以写 Lisp 程序这件事有多么重要, 尤其是当这一事实经常被忽视时. 即使有经验的 Lisp
+程序员也很少认识到他们从这种语言风格里享受到的好处. 例如 \texttt{这} 就是为何 Lisp 宏如此有力.
+本书里描述的大部分技术都依赖于编写管理 Lisp 表达式的程序的能力.
%%% Local Variables:
%%% coding: utf-8
Modified: books/onlisp/3-functional_programming.tex
==============================================================================
--- books/onlisp/3-functional_programming.tex (original)
+++ books/onlisp/3-functional_programming.tex Thu Aug 30 09:34:36 2007
@@ -1,6 +1,18 @@
\chapter{函数型编程}
\label{chap:functional_programming}
+前一章解释了 Lisp 和 Lisp 程序是怎样构建自单一的基本材料: 函数. 与任何建筑材料一样,
+它的品质既影响我们所建造的东西的种类, 也影响我们建造它的方式.
+
+本章描述了 Lisp 界流行的那类构造方法. 这些方法的高度精巧允许我们去尝试更加雄心勃勃的那类程序.
+下一章将描述一类特别重要的程序, 现在对 Lisp 来说成为可能了:
+程序逐渐进化而不是以老式的先计划再实现的方法开发.
+
+\section{函数型设计}
+\label{sec:functional_design}
+
+
+
%%% Local Variables:
%%% coding: utf-8
%%% mode: latex
Added: books/onlisp/7-macros.tex
==============================================================================
--- (empty file)
+++ books/onlisp/7-macros.tex Thu Aug 30 09:34:36 2007
@@ -0,0 +1,11 @@
+\chapter{Macros}
+\label{chap:macros}
+
+\section{宏的依赖关系}
+\label{sec:dependence_on_macros}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
Modified: books/onlisp/onlisp.tex
==============================================================================
--- books/onlisp/onlisp.tex (original)
+++ books/onlisp/onlisp.tex Thu Aug 30 09:34:36 2007
@@ -12,12 +12,12 @@
\begin{titlepage}
\title{On Lisp\thanks{原书网站: \texttt{http://www.paulgraham.com/onlisp.html}}}
-\author{Paul Graham [著]
- \and Chun Tian (binghe) [译]
+\author{Paul Graham [原著]
+ \and Chun Tian (binghe) [翻译]
\thanks{E-mail: \texttt{binghe.lisp(a)gmail.com}}\\
NetEase.com, Inc.\\
杭州研究院
- \and Jianshi Huang [校]
+ \and Jianshi Huang [校对]
\thanks{E-mail: \texttt{jianshi.huang(a)gmail.com}}\\
電気通信大学 システム工学\\
本多研究室}
@@ -30,6 +30,7 @@
\include{1-the_extensible_language}
\include{2-functions}
\include{3-functional_programming}
+\include{7-macros}
\include{24-prolog}
\include{packages}
1
0
Author: ctian
Date: Wed Aug 29 07:39:10 2007
New Revision: 29
Modified:
books/onlisp/0-preface.tex
books/onlisp/onlisp.tex
Log:
Merge fix from Jianshi Huang, thanks.
Modified: books/onlisp/0-preface.tex
==============================================================================
--- books/onlisp/0-preface.tex (original)
+++ books/onlisp/0-preface.tex Wed Aug 29 07:39:10 2007
@@ -4,31 +4,30 @@
本书适用于那些想成为更好的 Lisp 程序员的人. 本书假设读者已经熟悉 Lisp, 但不要求已有广泛的编程经验.
最初几章里会包含一些知识回顾. 我希望这些章节也令有经验的 Lisp 程序员感兴趣, 因为它们以新的视角展示了熟知的主题.
-通常很难用一句话来表达一门编程语言的本质, 但 John Foderato 这句已经很接近了:
+通常很难用一句话来表达一门编程语言的本质, 但 John Foderato 的这句描述已经很到位了:
\begin{quote}
- Lisp 是一门可编程的编程语言.
+ Lisp 是一门可编程的编程语言. (Lisp is a programmable programming language.)
\end{quote}
-当然 Lisp 的特性绝不至这些, 但这种随心所欲使用 Lisp 的能力, 在很大程度上正是 Lisp 专家和新手的区别之处.
-其他程序员根据语言向下写程序, 而资深 Lisp 程序员用程序向上构造语言\footnote{译者注:
- 翻译可能不太准确, 原文是
+当然 Lisp 的特性绝不止这些, 但这种随心所欲使用 Lisp 的能力, 在很大程度上正是 Lisp 专家和新手的区别之处.
+和其他程序员自上而下写程序不同, 资深 Lisp 程序员从语言自下而上构建他们的程序\footnote{译者注: 原文是
``As well as writing their programs down toward the language,
experienced Lisp programmers build the language
up toward their programs.''}.
本书教授如何使用自下而上的编程风格, 因为这是 Lisp 最擅长的方式.
-\section*{自下而上的设计}
+\section*{自下而上的设计 (Bottom-up Design)}
\label{sec:bottom-up_design}
随着软件复杂度的增长, 自下而上设计的重要性也正在日益提高. 今天的程序可能不得不面对极其复杂甚至开放式的需求.
-在这种情况下,传统的自顶向下方法有时会失效. 一种新的编程风格由此诞生, 它和当前大部分计算机科学课程的思路截然不同:
+在这种情况下,传统的自上而下方法有时会失效. 一种新的编程风格由此诞生, 它和当前大部分计算机科学课程的思路截然不同:
一个自下而上的程序由一系列的层写成, 每一层都作为更上一层的编程语言. X Window 和 \TeX
就是这种程序设计风格的范例.
-本书的主题是双重的: 就是说 Lisp 对于以自下而上的风格编写程序来说是一种自然的语言,
-同时自下而上的风格也是编写 Lisp 程序的一种自然的方式. \textsl{论 Lisp} 因此将吸引两类读者.
+本书的主题是双重的: 就是说 Lisp 对于以自下而上的编程风格来说是一种自然的语言,
+同时自下而上的编程风格也是编写 Lisp 程序的一种自然的方式. \textsl{On Lisp} 因此将吸引两类读者.
对于那些有兴趣编写可扩展程序的人, 本书将告诉你如果有了正确的语言你能做什么. 对于 Lisp 程序员来说,
本书提供一套关于怎样使用 Lisp 才能发挥其最大优势的实践性说明.
@@ -37,23 +36,24 @@
理论上用任何语言都可以写出的自下而上风格的程序, 但 Lisp 对于这种编程风格来说是最自然的载体. 在 Lisp 里,
自下而上设计不是一种专用于少见的大型或困难的程序的特别技术. 任何程度的程序都可以部分地用这种方式编写.
-Lisp 从一开始就是种可扩展的语言. 这种语言本身基本上就是一个 Lisp 函数的集合, 这些函数和你自己定义的没有本质区别.
+Lisp 从一开始就是种可扩展的语言. 语言本身基本上就是一个 Lisp 函数的集合, 这些函数和你自己定义的没有本质区别.
更进一步的是, Lisp 函数可以表达成列表, 这也是 Lisp 的数据结构. 这就意味着你可以写能生成 Lisp 代码的 Lisp 函数.
-一个好的 Lisp 程序员必须懂得如何利用上述这种可能性的优势. 通常做到这点的方式是定义一种称为 \textsl{宏}
+一个好的 Lisp 程序员必须懂得如何利用上述的这种可能性. 通常的途径是定义一种称为 \textsl{宏}
的操作符. 驾驭宏是从编写正确的 Lisp 程序到编写漂亮的程序过程中最重要的一步. 入门级 Lisp
-书籍给宏留下的篇幅仅限于一个宏的简短的概述: 一个关于宏是什么的解释, 连带少许暗示你能用这种奇妙的东西做什么的示例.
+书籍给宏留下的篇幅仅限于一个宏的简短的概述: 一个关于宏是什么的解释, 连带少许示例暗示你能用它实现一些奇妙的东西.
不过在本书里这些奇妙的东西将得到特别的重视.
-本书的一个目标就是将所有那些人们至今都很难学到的使用宏的经验收集到一处.
+本书的一个目标就是收集所有那些人们至今都很难学到的使用宏的经验.
一般入门级 Lisp 书籍都不太强调 Lisp 和其他语言的区别, 当然这是可以理解的.
-它们不得不从那些大都被学校教导成只会用 Pascal 术语来思考程序的学生中获得信息.
-如果仔细解释这些区别的话只会加重混乱程度: 例如说 \texttt{defun} 虽然看起来像一个过程定义,
-但实际上是一个程序在写另一个程序然后生成一段代码生成了一个函数对象然后用函数定义时给出的第一个参数来索引它.
-
-本书的一个目的就是解释究竟什么使 Lisp 不同于其他语言. 当我刚开始的时候, 我知道, 当所有其他条件都相等时,
-我更倾向于用 Lisp 而不是 C 或 Pascal 或 Fortran 来写程序. 我也知道这不只是感觉的问题.
-但我意识到如果我真的准备要解释 Lisp 在某些场合下是更好的语言, 我就最好得准备解释下为什么了.
+它们不得不把信息传递给那些被调教成只会用 Pascal 术语来思考编程的学生们.
+如果非要仔细解释这些区别的话, 只会加重混乱程度: 例如 \texttt{defun} 虽然看起来像一个过程定义,
+但实际上是一个程序在写另一个程序然后生成一段代码:
+生成了一个函数对象然后用函数定义时给出的第一个参数作为它的索引.
+
+本书的一个目的就是解释究竟什么使 Lisp 不同于其他语言. 当我刚开始学 Lisp 的时候, 当所有其他条件都相等时,
+我知道我更倾向于用 Lisp 而不是 C 或 Pascal 或 Fortran 来写程序. 我也知道这不只是个人品位的问题.
+但我真的决定要解释 Lisp 在某些方面是更好的语言的时候, 我就得好好准备一下了.
当某些人问 Louis Armstrong 什么是爵士乐时, 他回答说 ``如果你问爵士乐是什么, 那你永远不会知道.''
但他确实以一种方式回答了这个问题: 他向人们 \textsl{展示} 了什么是爵士乐. 同样也只有一种方式来解释 Lisp
@@ -61,60 +61,60 @@
采用的都是那些你可以用任何语言编写的程序. \textsl{On Lisp} 处理的是那类你只能用 Lisp 来写的程序.
可扩展性, 自顶向下编程, 交互式开发, 源代码转换, 嵌入式语言---这些都是 Lisp 展示其高级特性的场合.
-当然, 从理论上讲, 任何图灵等价的编程语言都可以做到任何其他同类语言相同的事. 但那种程度的能力不是编程语言所关心的.
-理论上任何你能用编程语言做到的事也可以用图灵机来说, 但实际上在一个图灵机上编程得不偿失.
+当然, 从理论上讲, 任何图灵等价的编程语言都可以做到任何其他同类语言能做的事. 但那个所谓的能力不是指编程语言的能力.
+理论上任何你能用编程语言做到的事也可以用图灵机来做, 但实际上在图灵机上编程得不偿失.
所以当我说这本书是关于讲如何做那些其他语言不可能做到事情的时候, 我并非指数学意义上的 ``不可能'',
而是在事实上从编程语言角度去看的. 这就是说, 如果你不得不用 C 来写本书中的一些程序, 你可能需要先用 C
-写一个 Lisp 编译器. 举个例子, C 语言中的嵌入式 Prolog---你能想象这需要多少工作量吗?
+写一个 Lisp 编译器. 举个例子, C 语言中的嵌入 Prolog---你能想象这需要多少工作量吗?
第 24 章将说明如何用 180 行 Lisp 做到这点.
-我希望做到比简单地演示 Lisp 的威力更多的事, 尽管如此, 我也想解释 \textsl{为何} Lisp 与众不同.
-这将出现一个本质问题---过于本质而不得不使用诸如 ``符号计算'' 这样的术语来回答.
-我将尽我所能地试图尽可能清晰地解释这些问题.
+我希望能比单单演示 Lisp 的强大做得更多一些, 尽管如此, 我也想解释 \textsl{为何} Lisp 与众不同.
+这是一个更本质的问题---难以解释而不得不使用诸如 ``符号计算'' 这样的术语来回答.
+我将尽可能清晰地解释这些问题.
\section*{本书计划}
\label{sec:plan_of_the_book}
由于函数是 Lisp 编程的基础, 本书以一些有关函数的章节开始.
-第 2 章解释 Lisp 函数究竟是什么以及他们所提供的可能性.
-第 3 章然后讨论函数型编程的优点, 这是 Lisp 程序最突出的风格.
+第 2 章解释 Lisp 函数究竟是什么以及他们所提供的编程方式的可能性.
+然后第 3 章然后讨论函数型编程的优点, 这是 Lisp 程序最主要的风格.
第 4 章展示如何用函数来扩展 Lisp.
-第 5 章建议了一种新类型的抽象让我们可以定义那些返回其他函数的函数.
+第 5 章建议了一种新的抽象方式, 返回其他函数的函数.
最后, 第 6 章显示了怎样使用函数来代替传统的数据结构.
-相比函数, 本书更加关注宏. 宏得到更多的关注部分是因为宏本身就有更多内容,
-部分是因为它们至今还没有适当的书面描述. 第 7--10 章形成一个宏技术的完整指导.
-结束的时候你将了解一个有经验的 Lisp 程序员所知的关于宏的大多数内容: 它们如何工作; 怎样定义, 测试, 以及调试它们;
-什么时候使用以及不使用宏; 宏的主要类型; 怎样写生成宏展开代码的程序; 宏风格一般如何区别于 Lisp 风格;
-以及怎样检测和修复每一种影响宏的唯一性问题.
+本书剩下的篇幅则更加关注宏. 因为宏本身就有更多内容,
+部分是因为它们至今还没有适当的出版物. 第 7--10 章形成一个完整的宏的指导教程.
+完成后你将了解一个有经验的 Lisp 程序员所知的关于宏的大多数内容: 它们如何工作; 怎样定义, 测试, 以及调试它们;
+何时应该使用以及何时不应该使用宏; 宏的主要类型; 怎样写生成宏展开代码的程序; 宏风格一般如何区别于 Lisp 风格;
+以及怎样检测和修复每一种影响宏的独特的问题.
-紧跟着这些指导, 第 11--18 章展示了一些可以用宏构造出来的强有力的抽象. 第 11 章展示如何写经典宏---
-那些创造上下文, 或者实现循环或条件判断的宏. 第 12 章解释宏在生成变量操作中的角色. 第 13
+随后, 第 11--18 章展示了一些可以用宏来构造的强有力的抽象. 第 11 章展示如何写经典的宏---
+那些创造上下文, 或者实现循环或条件判断的宏. 第 12 章解释宏在操作普通变量中的角色. 第 13
章展示宏如何通过将计算转移到编译期来使程序运行得更快. 第 14 章介绍了 anaphoric(首语重复) 宏,
可以允许你在程序里使用代词. 第 15 章展示了宏如何为第 5 章里定义的函数生成器提供一个更便利的接口.
-第 16 章展示了如何使用可以定义宏的宏来让 Lisp 为你写程序. 第 17 章讨论读取宏, 以及第 18 章, 解构宏.
+第 16 章展示了如何使用定义宏的宏来让 Lisp 为你写程序. 第 17 章讨论读取宏, 以及第 18 章, 解构宏.
第 19 章开始了本书的第四部分, 转向嵌入式语言. 第 19 章通过展示同一个程序, 一个回答数据库查询的程序,
-先是用解释器, 然后用真正的嵌入式语言, 来介绍这一主题.第 20 章展示了如何将 continuation 概念引入
-Common Lisp 程序, 这是一种描述针对计算的提示的那种对象. Continuation 是一个强有力的工具,
+先是用解释器, 然后用真正的嵌入式语言, 来介绍这一主题.第 20 章展示了如何将续延 (continuation) 概念引入
+Common Lisp 程序, 这是一种描述延续性计算的对象. 续延是一个强有力的工具,
可以用来实现多处理和非确定性选择. 将这些控制结构嵌入到 Lisp 中的讨论分别在第 21 和 22 章.
非确定性允许你写出有先见之明的程序, 听起来就像一种不寻常力量的抽象. 第 23 和 24 章展示了两种嵌入式语言,
-让非确定性达到其存在的意义: 总共 200 行代码的一个完整的 ATN 解析器, 以及一个嵌入式 Prolog.
+展示非确定性的存在意义: 一个完整的 ATN 解析器, 以及一个嵌入式 Prolog, 总共才 200 行代码.
-这些程序相对短小的事实对它们本身来说并无意义. 如果你诉诸于编写无法理解的代码, 无人能告诉你 200 行代码能做干什么.
-关键在于, 这些程序并不短, 因为它们依赖于编程技巧, 只是由于它们恰好用 Lisp 写成. 第 23 和 24
+这些程序的长短对它们本身来说并无意义. 如果你诉诸于编写无法理解的代码, 无人能告诉你 200 行代码能做干什么.
+关键在于, 这些程序并不是因为依赖于编程技巧才变得短小, 而是由于它们是以 Lisp 固有的, 自然的方式写成. 第 23 和 24
章的关键之处不是如何用一页代码实现 ATN 解析器或者用两页实现 Prolog, 而是想说明这些程序,
-当给出它们最自然的 Lisp 实现的时候是如此的简短. 后面这两个章节的嵌入式语言用实例证明了我开始时的观点:
-Lisp 对于以自下而上的风格编写程序来说是一种自然的语言, 同时自下而上的风格也是编写 Lisp 程序的一种自然的方式.
+当给出它们最自然的 Lisp 实现的时候是如此的简短. 后面这两个章节的嵌入式语言用实例证明了我开始时的双重观点:
+Lisp 对于以自下而上的编程风格来说是一种自然的语言, 同时自下而上的编程风格也是编写 Lisp 程序的一种自然的方式.
-本书以一个关于面向对象编程的讨论结束, 尤其是 \textsc{CLOS}, Common Lisp 对象系统. 通过将这一主题留到最后,
-我们可以更加清楚地看到, 面向对象的编程方式是一种 Lisp 已经展示过的扩展思想. 它也是可以 \textsl{在 Lisp 上}
-构造出来的多种抽象之一.
-
-一个章节的有价值的注释开始于第 \pageref{chap:notes} 页. 这些注释里包括参考文献, 附加或者替换的代码,
-或者是 Lisp 方面跟内容无关的一些描述. 注释是用页面边界外的小圆圈标注出来的, 就像这样\footnote{
- 目前还做不出来, 不会...}. 另外还有一个关于包 (packages) 的附录, 在第 \pageref{chap:packages} 页.
+本书以关于面向对象编程的讨论结束, 特别讨论了 \textsc{CLOS}, Common Lisp 对象系统. 通过将这一主题留到最后,
+我们可以更加清楚地看到, 面向对象的编程方式是一种已经存在于 Lisp 的思想的扩展. 它是多种可以建立
+\textsl{在 Lisp 上} 的抽象之一.
+
+自成一章的注释开始于第 \pageref{chap:notes} 页. 这些注释里包括参考文献, 附加或者替换的代码,
+或者是有关 Lisp 的但跟主题无关的一些描述. 注释是用页面边界外的小圆圈标注出来的, 就像这样
+\footnote{目前还做不出来, 不会...}. 另外还有一个关于包 (packages) 的附录, 在第 \pageref{chap:packages} 页.
正如一次纽约的观光旅游可能是一次世界上大多数文化的观光那样, 一次对 Lisp
作为可扩展编程语言的学习也能勾画出大部分 Lisp 技术. 这里描述的大多数技术通常都被 Lisp 社区所了解,
@@ -132,10 +132,12 @@
本书包含几百个示例, 范围从简单的表达式到可运行的 Prolog 实现. 本书中任何位置的代码, 都被写成可以在任何版本的
Common Lisp 上运行\footnote{译者注: 翻译版中的将重新确保所有代码都可以在当前的 Common Lisp 标准下运行,
并且在 SBCL, LispWorks 等主要平台下测试, 所有这些代码修改也会通过脚注明确标示出来.}.
-那些极少数需要用到不在 \textsc{CLTL1} 规范中实现的特性的示例将会被明显地在文本中标记出来.
+那些极少数需要用到的 \textsc{CLTL1} 规范之外的特性的示例将会被明确地在文本中标记出来.
+最后几个章节里包括一些 Scheme 的示例代码, 这些也会被清楚的标示出来.
所有代码可以通过匿名 \textsc{FTP} 从 \texttt{endor.harvard.edu} 下载, 在 \texttt{pub/onlisp}
-目录里. 问题和评论可以发到 \texttt{onlisp(a)das.harvard.edu}.
+目录里. 问题和评论可以发到 \texttt{onlisp(a)das.harvard.edu}. 中文用户可以发到水木社区 BBS
+(\texttt{bbs.newsmth.net}) 的 \texttt{FuncProgram} 板.
\section*{致谢}
\label{sec:acknowledgements}
Modified: books/onlisp/onlisp.tex
==============================================================================
--- books/onlisp/onlisp.tex (original)
+++ books/onlisp/onlisp.tex Wed Aug 29 07:39:10 2007
@@ -11,9 +11,16 @@
\newcommand{\sq}{\texttt{\#'}}
\begin{titlepage}
-\title{On Lisp\footnote{原书地址: \texttt{http://www.paulgraham.com/onlisp.html}}}
-\author{Paul Graham 著
- \and Chun Tian (binghe) 译\footnote{\texttt{E-mail: binghe.lisp(a)gmail.com}}}
+\title{On Lisp\thanks{原书网站: \texttt{http://www.paulgraham.com/onlisp.html}}}
+\author{Paul Graham [著]
+ \and Chun Tian (binghe) [译]
+ \thanks{E-mail: \texttt{binghe.lisp(a)gmail.com}}\\
+ NetEase.com, Inc.\\
+ 杭州研究院
+ \and Jianshi Huang [校]
+ \thanks{E-mail: \texttt{jianshi.huang(a)gmail.com}}\\
+ 電気通信大学 システム工学\\
+ 本多研究室}
\end{titlepage}
\maketitle
1
0
Author: ctian
Date: Wed Aug 29 06:57:08 2007
New Revision: 28
Modified:
books/onlisp/2-functions.tex
Log:
more chap 2
Modified: books/onlisp/2-functions.tex
==============================================================================
--- books/onlisp/2-functions.tex (original)
+++ books/onlisp/2-functions.tex Wed Aug 29 06:57:08 2007
@@ -274,7 +274,246 @@
\section{作用域}
\label{sec:scope}
-Common Lisp 是一种词法作用域 (lexically scope) 的 Lisp. Scheme 是最早的词法作用域方言; 在 Scheme 之前
+Common Lisp 是一种词法作用域 (lexically scope) 的 Lisp. Scheme 是最早的词法作用域方言; 在 Scheme 以前,
+动态作用域 (dynamic scope) 被看作 Lisp 的一种明确定义的风格.
+
+词法和动态作用域的区别取决于一个实现如何处理自由变量. 我们称一个符号在表达式里是约束 (bound) 的,
+当且仅当它被作为变量建立起来, 这可以来自参数, 也可以来自像 \texttt{let} 和 \texttt{do} 这样的变量绑定操作符.
+不受约束的符号, 就说它是 \textsl{自由} 的. 下面的例子具体说明了作用域:
+\begin{verbatim}
+(let ((y 7))
+ (defun scope-test (x)
+ (list x y)))
+\end{verbatim}
+在函数表达式里, x 是受约束的, 而 y 是自由的. 自由变量的有趣之处在于它们应有的值并不是很明显.
+一个约束变量的值是毫无疑问的---当 \texttt{scope-test} 被调用时, x 的值就是通过参数传给它的值.
+但 y 的值应该是什么呢? 这个问题要看具体方言的作用域规则.
+
+在动态作用域的 Lisp 里, 要想找出当 \texttt{scope-test} 执行时自由变量的值, 我们要回头检查函数的调用链.
+当我们发现一个 y 被约束的环境是, 那个 y 的绑定就将是 \texttt{scope-test} 里使用的那个. 如果我们没有发现,
+那我们就取 y 的全局值. 这样, 在一个动态作用域的 Lisp 里, 在调用的时候 y 将会产生这样的值:
+\begin{verbatim}
+> (let ((y 5))
+ (scope-test 3))
+(3 5)
+\end{verbatim}
+在动态作用域里, 当 \texttt{scope-test} 被定义时 y 被绑定到 7 就没有任何意义了. 当 \texttt{scope-test}
+被调用时 y 只有一个值, 就是 5.
+
+在词法作用域的 Lisp 里, 和回头检查函数的调用链不一样的是, 我们回头检查当这个函数被 \textsl{定义} 时所处的环境.
+在一个词法作用域 Lisp 里, 我们的示例将捕捉到当 \texttt{scope-test} 被定义时变量 y 的绑定.
+所以下面的事情将会发生在 Common Lisp 里:
+\begin{verbatim}
+> (let ((y 5))
+ (scope-test 3))
+(3 7)
+\end{verbatim}
+这里将 y 绑定到 5 在调用时对返回值没有任何效果.
+
+尽管你仍然可以通过将变量声明为 \textsl{special} 来得到动态作用域, 词法作用域是 Common Lisp 的默认行为.
+整体来看, Lisp 社区看待动态作用域的过时几乎没什么惋惜之情. 有一点就是它经常会导致痛苦而又难以捉摸的 bug.
+但词法作用域不只是一种避免错误的方式而已. 下一章会看到, 它也同时使某种崭新的编程技术成为可能.
+
+\section{闭包}
+\label{sec:closures}
+
+由于 Common Lisp 是词法作用域的, 当我们定义一个包含自由变量的函数时, 系统必须在函数定义的时候保存那些变量的绑定.
+这种函数和一组变量绑定的组合称为\textsl{闭包}. 闭包被用于广泛的应用场合.
+
+闭包在 Common Lisp 程序中如此无所不在以至于你可能已经用了却不知情. 每当你给 \texttt{mapcar}
+一个包含自由变量的前缀 \sq 的 \lexpr 时, 你就在使用闭包. 例如, 假设我们想写一个函数,
+它接受一个数列并且给每个数增加确定的数量. 这个 \texttt{list+} 函数
+\begin{verbatim}
+(defun list+ (lst n)
+ (mapcar #'(lambda (x) (+ x n))
+ lst))
+\end{verbatim}
+将做到我们想要的:
+\begin{verbatim}
+> (list+ '(1 2 3) 10)
+(11 12 13)
+\end{verbatim}
+如果我们仔细观察 \texttt{list+} 里传给 \texttt{mapcar} 的那个函数, 它实际上是个闭包. 那个 n 是自由的,
+它的绑定来自周围的环境. 在词法作用域下, 映射函数的每一次使用都将导致创建一个闭包.\footnote{
+在动态作用域下, 同样的说法却来自不同的原因---由于 \texttt{mapcar} 的任一参数都不叫做 x.}
+
+闭包在 Abelson 和 Sussman 的经验教材 \textsl{Structure and Interpretation of Computer Programs}
+一书中担当了更加重要的角色. 闭包是带有本地状态的函数. 使用这种状态最简单的方式是如下的情况:
+\begin{verbatim}
+(let ((counter 0))
+ (defun new-id () (incf counter))
+ (defun reset-id () (setq counter 0)))
+\end{verbatim}
+这两个函数共享一个计数器变量. 前者返回计数器的下一个值, 后者把计数器重置到 0.
+这种方式避免了对计数器变量非预期的引用, 尽管同样的事情也可以用一个全局的计数器变量做到.
+
+能返回一个带有本地状态的函数也是很有用的. 例如这个 \texttt{make-adder} 函数
+\begin{verbatim}
+(defun make-adder (n)
+ #'(lambda (x) (+ x n)))
+\end{verbatim}
+接受一个数值参数, 然后返回一个闭包, 当后者被调用时, 能够把之前那个数加到它的参数上.
+我们可以根据需要生成任意数量的这种加法器:
+\begin{verbatim}
+> (setq add2 (make-adder 2)
+ add10 (make-adder 10))
+#<Interpreted Function "LAMBDA (N)" {58121711}>
+> (funcall add2 5)
+7
+> (funcall add10 3)
+13
+\end{verbatim}
+在 \texttt{make-adder} 返回的那些闭包里, 内部状态都是固定的, 但其实也有可能生成那种可以要求改变他们状态的闭包.
+\begin{verbatim}
+(defun make-adderb (n)
+ #'(lambda (x &optional change)
+ (if change
+ (setq n x)
+ (+ x n))))
+\end{verbatim}
+这个新版本的 \texttt{make-adder} 函数返回一个闭包, 当以一个参数被调用时, 其行为就跟旧版本的一样.
+\begin{verbatim}
+> (setq addx (make-adderb 1))
+#<Interpreted Function "LAMBDA (N)" {5812A2F9}>
+> (funcall addx 3)
+4
+\end{verbatim}
+尽管如此, 当这个新型的加法器用非空的第二个参数调用时, 它内部的 n 的拷贝将被重置成由第一个参数指定的值:
+\begin{verbatim}
+> (funcall addx 100 t)
+100
+> (funcall addx 3)
+103
+\end{verbatim}
+
+甚至有可能返回共享同一数据对象的一组闭包. 图 \ref{fig:make-dbms} 包含有一个创建原始数据库的函数.
+它接受一个关联表 (\texttt{db}), 并且相应地返回一个由查询, 追加和删除这三个闭包所组成的列表.
+
+\begin{figure}
+\begin{verbatim}
+(defun make-dbms (db)
+ (list
+ #'(lambda (key)
+ (cdr (assoc key db)))
+ #'(lambda (key val)
+ (push (cons key val) db)
+ key)
+ #'(lambda (key)
+ (setf db (delete key db :key #'car))
+ key)))
+\end{verbatim}
+\caption{\label{fig:make-dbms}一个列表里的三个闭包}
+\end{figure}
+
+对 \texttt{make-dbms} 的每次调用创建一个新数据库---封闭在共享的关联表之上的一组新函数.
+\begin{verbatim}
+> (setq cities (make-dbms '((boston . us) (paris . france))))
+(#<Interpreted Function "LAMBDA (DB)" {581345C9}>
+ #<Interpreted Function "LAMBDA (DB)" {58134619}>
+ #<Interpreted Function "LAMBDA (DB)" {58134669}>)
+\end{verbatim}
+数据库里实际的关联表对外界是不可见的, 我们甚至不知道它是个关联表---但是它可以通过组成 \texttt{cities}
+的那些函数访问到:
+\begin{verbatim}
+> (funcall (car cities) 'boston)
+US
+> (funcall (second cities) 'london 'england)
+LONDON
+> (funcall (car cities) 'london)
+ENGLAND
+\end{verbatim}
+调用一个列表的 \texttt{car} 多少有些难看. 实际的程序中, 函数访问的入口可能隐藏在结构体里.
+当然也可以设法更清晰地使用它们---数据库可以间接地通过类似这样的函数访问:
+\begin{verbatim}
+(defun lookup (key db)
+ (funcall (car db) key))
+\end{verbatim}
+尽管如此, 闭包的行为不会受到如此包装的影响.
+
+实际程序中的闭包和数据结构往往比我们在 \texttt{make-adder} 和 \texttt{make-dbms} 里看到的更为精巧.
+这里用到的单个共享变量也可以发展成任意数量的变量,每个都可以约束到任何数据结构上。
+
+闭包是 Lisp 的一个明确和独特的优势. 某些 Lisp 程序, 如果努努力的话, 还有可能翻译到不那么强大的语言上,
+但只要试着去翻译上面那些使用了闭包的程序, 就会明白这种抽象帮我们省去了多少工作.
+后续章节将继续探讨闭包的更多细节. 第 5 章展示了如何用它们构造复合函数, 然后第 6
+章里请看它们如何用来替代传统的数据结构.
+
+\section{本地函数}
+\label{sec:local_functions}
+
+当我们用 \lexpr 来定义函数时, 我们就会面对一个使用 \texttt{defun} 时所没有的限制: 一个用 \lexpr
+定义的函数由于没有名字, 因此也就没有办法引用其自身. 这意味着在 Common Lisp 里我们不能使用 \texttt{lambda}
+来定义递归函数.
+
+如果我们想要应用某些函数到一个列表的所有元素上, 我们可以使用最熟悉的 Lisp 语句:
+\begin{verbatim}
+> (mapcar #'(lambda (x) (+ 2 x))
+ '(2 5 7 3))
+(4 7 9 5)
+\end{verbatim}
+如果我们想要把一个递归函数作为第一个参数送给 \texttt{mapcar} 呢? 如果函数已经用 \texttt{defun} 定义了,
+我们就可以通过名字简单地引用它:
+\begin{verbatim}
+> (mapcar #'copy-tree '((a b) (c d e)))
+((A B) (C D E))
+\end{verbatim}
+但现在假设这个函数必须是一个闭包, 带有从 \texttt{mapcar} 发生时所处环境的一些绑定. 在我们的 \texttt{list+}
+例子里,
+\begin{verbatim}
+(defun list+ (lst n)
+ (mapcar #'(lambda (x) (+ x n))
+ lst))
+\end{verbatim}
+\texttt{mapcar} 的第一个参数, \texttt{\sq(lambda (x) (+ x n))}, 必须定义在 \texttt{list+}
+里因为它需要捕捉 \texttt{n} 的绑定. 到目前为止都还算好, 但如果我们要给 \texttt{mapcar}
+传递的函数在需要本地状态 \textsl{同时} 也需要递归呢? 我们不能使用一个在其他地方通过 \texttt{defun}
+定义的函数, 因为我们需要本地环境的绑定. 并且我们也不能使用 \texttt{lambda} 来定义一个递归函数,
+因为这个函数将没有办法引用其自身.
+
+Common Lisp 提供了一个 \texttt{labels} 以解决这一两难困境. 作为重要的保留, \texttt{labels}
+有点儿像函数的 \texttt{let}. \texttt{labels} 表达式里的每一个绑定规范必须是如下形式:
+\begin{verbatim}
+(<name> <parameters> . <body>)
+\end{verbatim}
+在 \texttt{labels} 表达式里, \texttt{<name>} 将指向等价于下面这样的函数:
+\begin{verbatim}
+#'(lambda <parameters> . <body>)
+\end{verbatim}
+例如,
+\begin{verbatim}
+> (labels ((inc (x) (1+ x)))
+ (inc 3))
+> 4
+\end{verbatim}
+尽管如此, 在 \texttt{let} 与 \texttt{labels} 之间有一个重要区别. 在 \texttt{let} 表达式里,
+一个变量的值不能依赖于同一个 \texttt{let} 里生成的另一个变量---就是说, 你不能说
+\begin{verbatim}
+(let ((x 10) (y x))
+ y)
+\end{verbatim}
+然后期待这个新的 $y$ 能反映出那个新 $x$ 的值来. 相反, 在 \texttt{labels} 里定义的函数 $f$
+的函数体里就可以引用那里定义的其他函数, 包括 $f$ 本身, 这就使定义递归函数成为可能.
+
+使用 \texttt{labels} 我们就可以写出类似 \texttt{list+} 这样的函数了, 但这里 \texttt{mapcar}
+的第一个函数是递归函数:
+\begin{verbatim}
+(defun count-instances (obj lsts)
+ (labels ((instances-in (lst)
+ (if (consp lst)
+ (+ (if (eq (car lst) obj) 1 0)
+ (instances-in (cdr lst)))
+ 0)))
+ (mapcar #'instances-in lsts)))
+\end{verbatim}
+该函数接受一个对象和一个列表, 然后返回该对象在列表的每个元素(作为列表)中出现的次数, 所组成的列表:
+\begin{verbatim}
+> (count-instances 'a '((a b c) (d a r p a) (d a r) (a a)))
+(1 2 1 2)
+\end{verbatim}
+
+\section{尾递归}
+\label{sec:tail-recursion}
+
%%% Local Variables:
%%% coding: utf-8
1
0
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
1
0
Author: ctian
Date: Tue Aug 28 10:33:26 2007
New Revision: 26
Modified:
books/onlisp/0-preface.tex
books/onlisp/1-the_extensible_language.tex
Log:
Take suggest from Huang Jianshi
Modified: books/onlisp/0-preface.tex
==============================================================================
--- books/onlisp/0-preface.tex (original)
+++ books/onlisp/0-preface.tex Tue Aug 28 10:33:26 2007
@@ -17,26 +17,26 @@
experienced Lisp programmers build the language
up toward their programs.''}.
-本书教授如何使用自底向上的编程风格, 因为这是 Lisp 最擅长的方式.
+本书教授如何使用自下而上的编程风格, 因为这是 Lisp 最擅长的方式.
-\section*{自底向上的设计}
+\section*{自下而上的设计}
\label{sec:bottom-up_design}
-随着软件复杂度的增长, 自底向上设计的重要性也正在日益提高. 今天的程序可能不得不面对极其复杂甚至开放式的需求.
+随着软件复杂度的增长, 自下而上设计的重要性也正在日益提高. 今天的程序可能不得不面对极其复杂甚至开放式的需求.
在这种情况下,传统的自顶向下方法有时会失效. 一种新的编程风格由此诞生, 它和当前大部分计算机科学课程的思路截然不同:
-一个自底向上的程序由一系列的层写成, 每一层都作为更上一层的编程语言. X Window 和 \TeX
+一个自下而上的程序由一系列的层写成, 每一层都作为更上一层的编程语言. X Window 和 \TeX
就是这种程序设计风格的范例.
-本书的主题是双重的: 就是说 Lisp 对于以自底向上的风格编写程序来说是一种自然的语言,
-同时自底向上的风格也是编写 Lisp 程序的一种自然的方式. \textsl{论 Lisp} 因此将吸引两类读者.
+本书的主题是双重的: 就是说 Lisp 对于以自下而上的风格编写程序来说是一种自然的语言,
+同时自下而上的风格也是编写 Lisp 程序的一种自然的方式. \textsl{论 Lisp} 因此将吸引两类读者.
对于那些有兴趣编写可扩展程序的人, 本书将告诉你如果有了正确的语言你能做什么. 对于 Lisp 程序员来说,
本书提供一套关于怎样使用 Lisp 才能发挥其最大优势的实践性说明.
本书的标题是为了强调自底向下编程对于 Lisp 的重要性. 相比只是用 Lisp 编写你的程序, 你还可以 \textsl{用 Lisp}
写你自己的语言, 然后再用来写你的程序.
-理论上用任何语言都可以写出的自底向上风格的程序, 但 Lisp 对于这种编程风格来说是最自然的载体. 在 Lisp 里,
-自底向上设计不是一种专用于少见的大型或困难的程序的特别技术. 任何程度的程序都可以部分地用这种方式编写.
+理论上用任何语言都可以写出的自下而上风格的程序, 但 Lisp 对于这种编程风格来说是最自然的载体. 在 Lisp 里,
+自下而上设计不是一种专用于少见的大型或困难的程序的特别技术. 任何程度的程序都可以部分地用这种方式编写.
Lisp 从一开始就是种可扩展的语言. 这种语言本身基本上就是一个 Lisp 函数的集合, 这些函数和你自己定义的没有本质区别.
更进一步的是, Lisp 函数可以表达成列表, 这也是 Lisp 的数据结构. 这就意味着你可以写能生成 Lisp 代码的 Lisp 函数.
@@ -106,7 +106,7 @@
关键在于, 这些程序并不短, 因为它们依赖于编程技巧, 只是由于它们恰好用 Lisp 写成. 第 23 和 24
章的关键之处不是如何用一页代码实现 ATN 解析器或者用两页实现 Prolog, 而是想说明这些程序,
当给出它们最自然的 Lisp 实现的时候是如此的简短. 后面这两个章节的嵌入式语言用实例证明了我开始时的观点:
-Lisp 对于以自底向上的风格编写程序来说是一种自然的语言, 同时自底向上的风格也是编写 Lisp 程序的一种自然的方式.
+Lisp 对于以自下而上的风格编写程序来说是一种自然的语言, 同时自下而上的风格也是编写 Lisp 程序的一种自然的方式.
本书以一个关于面向对象编程的讨论结束, 尤其是 \textsc{CLOS}, Common Lisp 对象系统. 通过将这一主题留到最后,
我们可以更加清楚地看到, 面向对象的编程方式是一种 Lisp 已经展示过的扩展思想. 它也是可以 \textsl{在 Lisp 上}
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 10:33:26 2007
@@ -56,7 +56,7 @@
你可能会对语言和应用程序之间的衔接过于敏感以至于你再能够回到另一种语言,
那种你感觉并不能给你很多你所需要的灵活性的语言.
-\section{自底向上编程}
+\section{自下而上编程}
\label{sec:programming_bottom-up}
一个长期存在的编程原则是作为程序的功能性单元不应该过于庞大. 如果程序里某些组件的规模增长超过了它可读的程度,
@@ -70,14 +70,14 @@
但也足够小到可以作为一个基本单元来理解.
有经验的 Lisp 程序员用另一种不同的方式来细化他们的程序. 类似自顶向下的设计那样, 他们根据一种称为
-\textsl{自底向上的设计} 原则来处理---通过改变语言来适应程序. 在 Lisp 中, 你不仅是根据语言向下写程序,
+\textsl{自下而上的设计} 原则来处理---通过改变语言来适应程序. 在 Lisp 中, 你不仅是根据语言向下写程序,
也可以根据程序向上构造语言. 在你编程的时候你可能会想 ``我希望 Lisp 这样或者那样的操作符.''
于是你就可以直接去实现它. 后来你意识到使用新的操作符可以简化程序中另一部分的设计, 诸如此类. 语言和程序一同演变.
就像交战两国的边界一样, 语言和程序的界限不断地变化, 直到最终它们稳定在山川和河流的边缘,
这也就是你要解决的问题本身的自然边界. 最后你的程序看起来就好像语言就是为解决它而设计的.
并且当语言和程序都非常适应彼此时, 你得到的将是清晰, 短小和高效的代码.
-值得强调的是, 自底向上的设计并不意味着只是以不同的顺序写同一个程序. 当你以自底向上的方式工作时,
+值得强调的是, 自下而上的设计并不意味着只是以不同的顺序写同一个程序. 当你以自下而上的方式工作时,
你通常得到的是一个完全不同的程序. 你将得到一个带有更多抽象操作符的更大的语言, 和一个用它写的更小的程序,
而不是一个简单的整块的程序. 你将得到一个拱而不是梁.
@@ -86,22 +86,22 @@
\begin{enumerate}
\item
- 通过让语言做更多的工作, 自底向上设计产生的程序更加短小轻快. 一个更短小的程序就不必划分成许多的组件,
+ 通过让语言做更多的工作, 自下而上设计产生的程序更加短小轻快. 一个更短小的程序就不必划分成许多的组件,
并且更少的组件意味着程序更易于阅读和修改. 更少的组件也意味着更少的组件之间的连接, 这样也会减少错误发生的机会.
一个工业设计者往往努力地减少一台机器上运动部件的数量, 同样有经验的 Lisp
- 程序员使用自底向上的设计方法来减少他们程序的规模和复杂度.
+ 程序员使用自下而上的设计方法来减少他们程序的规模和复杂度.
\item
- 自底向上的设计促进了代码重用. 当你写两个或更多程序时, 许多你为第一个程序所写的工具也对后面的程序也有用.
+ 自下而上的设计促进了代码重用. 当你写两个或更多程序时, 许多你为第一个程序所写的工具也对后面的程序也有用.
一旦你攒下来雄厚的工具基础, 写一个新程序所花的成本相比从裸 Lisp 环境开始可能只有几分之一.
\item
- 自底向上的设计提高了程序的可读性. 一个这种类型的抽象要求读者去理解一个通用操作符,
+ 自下而上的设计提高了程序的可读性. 一个这种类型的抽象要求读者去理解一个通用操作符,
而一个具体的函数抽象则要求读者去理解一个专用的子例程.
\item
- 由于自底向上的设计导致你总是去关注代码中的模式, 这种工作方式有助于澄清设计程序时的思路.
+ 由于自下而上的设计导致你总是去关注代码中的模式, 这种工作方式有助于澄清设计程序时的思路.
如果一个程序中两个相距遥远的组件在形式上很相似, 你可能就会注意到这种相似性然后可能会以更简单的方式重新设计程序.
\end{enumerate}
-自底向上的设计对于其他非 Lisp 语言来说也能达到一定程度. 当你看到库函数的时候, 那就是自底向上设计的结果.
+自下而上的设计对于其他非 Lisp 语言来说也能达到一定程度. 当你看到库函数的时候, 那就是自下而上设计的结果.
尽管如此, Lisp 带给你该领域更广阔的力量, 在 Lisp 风格程序中语言这一角色重要性也成比例地增加了---
因此 Lisp 不仅是另一种不同的语言, 更是一整套完全不同的编程方式.
@@ -118,7 +118,7 @@
到他们的所有需求. 但如果我们不能给他们一个能做到他们想要的每一件事的软件, 我们可以给他们一个可扩展的软件.
我们把自己的软件从仅是一个程序转化成一个编程语言, 然后高级用户就可以在此基础上构建他们需要的额外特性.
-自底向上的设计很自然地产生了可扩展的程序. 最简单的自底向上程序包括两层: 语言和程序. 复杂程序可以被写成一系列层,
+自下而上的设计很自然地产生了可扩展的程序. 最简单的自下而上程序包括两层: 语言和程序. 复杂程序可以被写成一系列层,
每一层作为其上层的编程语言. 如果这一哲学被一直用到最上面的那层, 那这层对于用户来说就变成了一门编程语言.
这样一个可扩展性渗透到每一层次的程序, 相比那些先按照传统黑箱方法写成,
然后又想得到可扩展性的那些系统来说, 可以成为一个好得多的编程语言.
@@ -195,7 +195,7 @@
(例如算术或者 I/O) 继续被使用, 你只需要实现改变了的部分 (例如控制结构). 以这种方式实现的语言称为
\textsl{嵌入式语言}.
-嵌入式语言是自底向上程序设计的自然产物. Common Lisp 里已经包括几种. 其中最著名的, \textsc{CLOS},
+嵌入式语言是自下而上程序设计的自然产物. Common Lisp 里已经包括几种. 其中最著名的, \textsc{CLOS},
将在最后一章里讨论. 但你也可以定义自己的嵌入式语言. 你可以得到一个完全符合你程序需要的语言,
甚至它们最后看起来跟 Lisp 已经非常不同.
1
0
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}}}
\author{Paul Graham 著
1
0
Author: ctian
Date: Mon Aug 27 22:13:01 2007
New Revision: 24
Added:
books/
books/onlisp/
books/onlisp/0-preface.tex
books/onlisp/1-the_extensible_language.tex
books/onlisp/2-functions.tex
books/onlisp/24-prolog.tex
books/onlisp/3-functional_programming.tex
books/onlisp/notes.tex
books/onlisp/onlisp.kilepr
books/onlisp/onlisp.tex
books/onlisp/packages.tex
Log:
Add onlisp translate (zh_CN)
Added: books/onlisp/0-preface.tex
==============================================================================
--- (empty file)
+++ books/onlisp/0-preface.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,186 @@
+\chapter*{前言}
+\label{chap:preface}
+
+本书适用于那些想成为更好的 Lisp 程序员的人. 本书假设读者已经熟悉 Lisp, 但不要求已有广泛的编程经验.
+最初几章里会包含一些知识回顾. 我希望这些章节也令有经验的 Lisp 程序员感兴趣, 因为它们以新的视角展示了熟知的主题.
+
+通常很难用一句话来表达一门编程语言的本质, 但 John Foderato 这句已经很接近了:
+
+\begin{quote}
+ Lisp 是一门可编程的编程语言.
+\end{quote}
+
+当然 Lisp 的特性绝不至这些, 但这种随心所欲使用 Lisp 的能力, 在很大程度上正是 Lisp 专家和新手的区别之处.
+其他程序员根据语言向下写程序, 而资深 Lisp 程序员用程序向上构造语言\footnote{译者注:
+ 翻译可能不太准确, 原文是
+ ``As well as writing their programs down toward the language,
+ experienced Lisp programmers build the language
+ up toward their programs.''}.
+
+本书教授如何使用自底向上的编程风格, 因为这是 Lisp 最擅长的方式.
+
+\section*{自底向上的设计}
+\label{sec:bottom-up_design}
+
+随着软件复杂度的增长, 自底向上设计的重要性也正在日益提高. 今天的程序可能不得不面对极其复杂甚至开放式的需求.
+在这种情况下,传统的自顶向下方法有时会失效. 一种新的编程风格由此诞生, 它和当前大部分计算机科学课程的思路截然不同:
+一个自底向上的程序由一系列的层写成, 每一层都作为更上一层的编程语言. X Window 和 \TeX
+就是这种程序设计风格的范例.
+
+本书的主题是双重的: 就是说 Lisp 对于以自底向上的风格编写程序来说是一种自然的语言,
+同时自底向上的风格也是编写 Lisp 程序的一种自然的方式. \textsl{论 Lisp} 因此将吸引两类读者.
+对于那些有兴趣编写可扩展程序的人, 本书将告诉你如果有了正确的语言你能做什么. 对于 Lisp 程序员来说,
+本书提供一套关于怎样使用 Lisp 才能发挥其最大优势的实践性说明.
+
+本书的标题是为了强调自底向下编程对于 Lisp 的重要性. 相比只是用 Lisp 编写你的程序, 你还可以 \textsl{用 Lisp}
+写你自己的语言, 然后再用来写你的程序.
+
+理论上用任何语言都可以写出的自底向上风格的程序, 但 Lisp 对于这种编程风格来说是最自然的载体. 在 Lisp 里,
+自底向上设计不是一种专用于少见的大型或困难的程序的特别技术. 任何程度的程序都可以部分地用这种方式编写.
+Lisp 从一开始就是种可扩展的语言. 这种语言本身基本上就是一个 Lisp 函数的集合, 这些函数和你自己定义的没有本质区别.
+更进一步的是, Lisp 函数可以表达成列表, 这也是 Lisp 的数据结构. 这就意味着你可以写能生成 Lisp 代码的 Lisp 函数.
+
+一个好的 Lisp 程序员必须懂得如何利用上述这种可能性的优势. 通常做到这点的方式是定义一种称为 \textsl{宏}
+的操作符. 驾驭宏是从编写正确的 Lisp 程序到编写漂亮的程序过程中最重要的一步. 入门级 Lisp
+书籍给宏留下的篇幅仅限于一个宏的简短的概述: 一个关于宏是什么的解释, 连带少许暗示你能用这种奇妙的东西做什么的示例.
+不过在本书里这些奇妙的东西将得到特别的重视.
+本书的一个目标就是将所有那些人们至今都很难学到的使用宏的经验收集到一处.
+
+一般入门级 Lisp 书籍都不太强调 Lisp 和其他语言的区别, 当然这是可以理解的.
+它们不得不从那些大都被学校教导成只会用 Pascal 术语来思考程序的学生中获得信息.
+如果仔细解释这些区别的话只会加重混乱程度: 例如说 \texttt{defun} 虽然看起来像一个过程定义,
+但实际上是一个程序在写另一个程序然后生成一段代码生成了一个函数对象然后用函数定义时给出的第一个参数来索引它.
+
+本书的一个目的就是解释究竟什么使 Lisp 不同于其他语言. 当我刚开始的时候, 我知道, 当所有其他条件都相等时,
+我更倾向于用 Lisp 而不是 C 或 Pascal 或 Fortran 来写程序. 我也知道这不只是感觉的问题.
+但我意识到如果我真的准备要解释 Lisp 在某些场合下是更好的语言, 我就最好得准备解释下为什么了.
+
+当某些人问 Louis Armstrong 什么是爵士乐时, 他回答说 ``如果你问爵士乐是什么, 那你永远不会知道.''
+但他确实以一种方式回答了这个问题: 他向人们 \textsl{展示} 了什么是爵士乐. 同样也只有一种方式来解释 Lisp
+的威力---演示那些对于其他语言来说极其困难甚至不可能实现的技术. 多数关于编程的书籍---包括 Lisp 编程书籍---
+采用的都是那些你可以用任何语言编写的程序. \textsl{On Lisp} 处理的是那类你只能用 Lisp 来写的程序.
+可扩展性, 自顶向下编程, 交互式开发, 源代码转换, 嵌入式语言---这些都是 Lisp 展示其高级特性的场合.
+
+当然, 从理论上讲, 任何图灵等价的编程语言都可以做到任何其他同类语言相同的事. 但那种程度的能力不是编程语言所关心的.
+理论上任何你能用编程语言做到的事也可以用图灵机来说, 但实际上在一个图灵机上编程得不偿失.
+
+所以当我说这本书是关于讲如何做那些其他语言不可能做到事情的时候, 我并非指数学意义上的 ``不可能'',
+而是在事实上从编程语言角度去看的. 这就是说, 如果你不得不用 C 来写本书中的一些程序, 你可能需要先用 C
+写一个 Lisp 编译器. 举个例子, C 语言中的嵌入式 Prolog---你能想象这需要多少工作量吗?
+第 24 章将说明如何用 180 行 Lisp 做到这点.
+
+我希望做到比简单地演示 Lisp 的威力更多的事, 尽管如此, 我也想解释 \textsl{为何} Lisp 与众不同.
+这将出现一个本质问题---过于本质而不得不使用诸如 ``符号计算'' 这样的术语来回答.
+我将尽我所能地试图尽可能清晰地解释这些问题.
+
+\section*{本书计划}
+\label{sec:plan_of_the_book}
+
+由于函数是 Lisp 编程的基础, 本书以一些有关函数的章节开始.
+第 2 章解释 Lisp 函数究竟是什么以及他们所提供的可能性.
+第 3 章然后讨论函数型编程的优点, 这是 Lisp 程序最突出的风格.
+第 4 章展示如何用函数来扩展 Lisp.
+第 5 章建议了一种新类型的抽象让我们可以定义那些返回其他函数的函数.
+最后, 第 6 章显示了怎样使用函数来代替传统的数据结构.
+
+相比函数, 本书更加关注宏. 宏得到更多的关注部分是因为宏本身就有更多内容,
+部分是因为它们至今还没有适当的书面描述. 第 7--10 章形成一个宏技术的完整指导.
+结束的时候你将了解一个有经验的 Lisp 程序员所知的关于宏的大多数内容: 它们如何工作; 怎样定义, 测试, 以及调试它们;
+什么时候使用以及不使用宏; 宏的主要类型; 怎样写生成宏展开代码的程序; 宏风格一般如何区别于 Lisp 风格;
+以及怎样检测和修复每一种影响宏的唯一性问题.
+
+紧跟着这些指导, 第 11--18 章展示了一些可以用宏构造出来的强有力的抽象. 第 11 章展示如何写经典宏---
+那些创造上下文, 或者实现循环或条件判断的宏. 第 12 章解释宏在生成变量操作中的角色. 第 13
+章展示宏如何通过将计算转移到编译期来使程序运行得更快. 第 14 章介绍了 anaphoric(首语重复) 宏,
+可以允许你在程序里使用代词. 第 15 章展示了宏如何为第 5 章里定义的函数生成器提供一个更便利的接口.
+第 16 章展示了如何使用可以定义宏的宏来让 Lisp 为你写程序. 第 17 章讨论读取宏, 以及第 18 章, 解构宏.
+
+第 19 章开始了本书的第四部分, 转向嵌入式语言. 第 19 章通过展示同一个程序, 一个回答数据库查询的程序,
+先是用解释器, 然后用真正的嵌入式语言, 来介绍这一主题.第 20 章展示了如何将 continuation 概念引入
+Common Lisp 程序, 这是一种描述针对计算的提示的那种对象. Continuation 是一个强有力的工具,
+可以用来实现多处理和非确定性选择. 将这些控制结构嵌入到 Lisp 中的讨论分别在第 21 和 22 章.
+非确定性允许你写出有先见之明的程序, 听起来就像一种不寻常力量的抽象. 第 23 和 24 章展示了两种嵌入式语言,
+让非确定性达到其存在的意义: 总共 200 行代码的一个完整的 ATN 解析器, 以及一个嵌入式 Prolog.
+
+这些程序相对短小的事实对它们本身来说并无意义. 如果你诉诸于编写无法理解的代码, 无人能告诉你 200 行代码能做干什么.
+关键在于, 这些程序并不短, 因为它们依赖于编程技巧, 只是由于它们恰好用 Lisp 写成. 第 23 和 24
+章的关键之处不是如何用一页代码实现 ATN 解析器或者用两页实现 Prolog, 而是想说明这些程序,
+当给出它们最自然的 Lisp 实现的时候是如此的简短. 后面这两个章节的嵌入式语言用实例证明了我开始时的观点:
+Lisp 对于以自底向上的风格编写程序来说是一种自然的语言, 同时自底向上的风格也是编写 Lisp 程序的一种自然的方式.
+
+本书以一个关于面向对象编程的讨论结束, 尤其是 \textsc{CLOS}, Common Lisp 对象系统. 通过将这一主题留到最后,
+我们可以更加清楚地看到, 面向对象的编程方式是一种 Lisp 已经展示过的扩展思想. 它也是可以 \textsl{在 Lisp 上}
+构造出来的多种抽象之一.
+
+一个章节的有价值的注释开始于第 \pageref{chap:notes} 页. 这些注释里包括参考文献, 附加或者替换的代码,
+或者是 Lisp 方面跟内容无关的一些描述. 注释是用页面边界外的小圆圈标注出来的, 就像这样\footnote{
+ 目前还做不出来, 不会...}. 另外还有一个关于包 (packages) 的附录, 在第 \pageref{chap:packages} 页.
+
+正如一次纽约的观光旅游可能是一次世界上大多数文化的观光那样, 一次对 Lisp
+作为可扩展编程语言的学习也能勾画出大部分 Lisp 技术. 这里描述的大多数技术通常都被 Lisp 社区所了解,
+但很多内容至今也没有在任何地方有记载. 而一些问题, 例如宏的适当角色或者变量捕捉的本质, 甚至对于很有经验的
+Lisp 程序员来说也只有些模糊的理解.
+
+\section*{示例}
+\label{sec:examples}
+
+Lisp 是个语言家族. 由于 Common Lisp 仍然是广泛使用的方言, 本书的大部分示例都是用 Common Lisp 写的.
+这一语言最初于 1984 年被定义在 Guy Steele 写的的一本出版物 \textsl{Common Lisp: the Language}
+(\textsc{CLTL1}) 里. 这一定义在 1990 年该书第二版 (\textsc{CLTL2}) 的出版以后被代替了,
+\textsc{CLTL2} 可能会成为未来的 \textsc{ANSI} 标准.
+
+本书包含几百个示例, 范围从简单的表达式到可运行的 Prolog 实现. 本书中任何位置的代码, 都被写成可以在任何版本的
+Common Lisp 上运行\footnote{译者注: 翻译版中的将重新确保所有代码都可以在当前的 Common Lisp 标准下运行,
+并且在 SBCL, LispWorks 等主要平台下测试, 所有这些代码修改也会通过脚注明确标示出来.}.
+那些极少数需要用到不在 \textsc{CLTL1} 规范中实现的特性的示例将会被明显地在文本中标记出来.
+
+所有代码可以通过匿名 \textsc{FTP} 从 \texttt{endor.harvard.edu} 下载, 在 \texttt{pub/onlisp}
+目录里. 问题和评论可以发到 \texttt{onlisp(a)das.harvard.edu}.
+
+\section*{致谢}
+\label{sec:acknowledgements}
+
+写此书时, 我要特别感谢 Robert Morris 的帮助. 我经常去向他寻求建议并且每次都满载而归.
+本书的一些示例代码就源自他, 包括某页的 \texttt{for} 版本, 某页的 \texttt{aand} 版本, 某页的
+\texttt{match}, 某页的宽度优先 \texttt{true-choose}, 以及第 24.2 章的 Prolog 解释器.
+事实上, 整本书都 (有时基本是抄录) 反映了过去七年来我跟 Robert 之间的对话. (谢谢你, rtm!)
+
+我也要给予 David Moon 特别的感谢, 他仔细地阅读了大部分手稿并且给出许多非常有用的评论. 第 12
+章是按照他的建议完全重写了的, 某页关于变量捕捉的示例代码也是他提供的.
+
+我很幸运地拥有 David Touretzky 和 Skoma Brittain 这两位技术审稿人. 某些章节在他们的建议下被追加或者重写的.
+某些上给出的替代的真非确定性选择操作符就是基于了 David Touretzky 的一个建议.
+
+其他一些人欣然阅读了部分或全部的手稿, 包括 Tom Cheatham, Richard Draves (他在 1985 年也帮助重写了
+\texttt{alambda} 和 \texttt{propmacro}), John Foderaro, David Hendler, George Luger, Robert
+Muller, Mark Nitzberg, 以及 Guy Steele.
+
+我感谢 Cheatham 教授以及整个哈佛, 他们提供了让我撰写此书的条件. 也感谢 Aiken 实验室的全体成员, 包括
+Tony Hartman, Janusz Juda, Harry Bochner, 以及 Joanne Klys.
+
+Prentice Hall 的人做得很不错. 我为与 Alan Apt 这位优秀的编辑和好伙伴一起共事感到幸运. 同时也感谢
+Mona Pompili, Shirley Michaels, 以及 Shirley McGuire 的组织工作和他们的幽默.
+
+剑桥 Bow and Arrow 出版社的无与伦比的 Gino Lee 制作了封面. 封面上的那颗树暗示了某页上的观点.
+
+本书使用 \LaTeX 排版, 这是一种由 Leslie Lamport 在 Donald Knuth 的 \TeX 基础上设计的语言,
+使用了来自 L. A. Carr, Van Jacobson 和 Scott Stanton 的附加宏. 插图由 John Vlissides 和
+Scott Stanton 设计的 Idraw 完成. 整本书用 L. Peter Deutsch 的 Ghostscript 生成之后, 在 Tim Theisen
+的 Ghostview 里预览. Chiron Inc. 公司的 Gary Bisbee 制作了影印版.
+
+我严重感谢许多其他人, 包括 Paul Becker, Phil Chapnick, Alice Hartley, Glenn Holloway, Meichun Hsu,
+Krzysztof Lenk, Arman Maghbouleh, Howard Mullings, Nancy Parmet, Robert Penny, Gary Sabot,
+Patrick Slaney, Steve Strassman, Dave Watkins, Weickers 一家, 和 Bill Woods.
+
+最后, 我要感谢我的父母, 为他们的榜样和鼓励; 还有 Jackie, 他们教给我那些如果我听从他们的教诲就可以学到的知识.
+
+我希望阅读此书是件快乐的事. 在所有我知道的语言中, 我最喜欢 Lisp, 基本上是因为它是最优美的. 本书正是关于最
+Lisp 化的 Lisp. 写这本书很有趣, 我希望它能反映在字里行间.
+
+\textsl{Paul Graham}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
Added: books/onlisp/1-the_extensible_language.tex
==============================================================================
--- (empty file)
+++ books/onlisp/1-the_extensible_language.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,239 @@
+\chapter{可扩展语言}
+\label{chap:the_extensible_language}
+
+不久以前当你问 Lisp 能做什么时, 很多人会回答说``用于人工智能''. 事实上, Lisp 和人工智能之间的联系只是历史原因.
+Lisp 由 John McCarthy 发明, 他也是 ``人工智能'' 这一术语的发明人. 他的学生和同事们用 Lisp 写程序,
+于是它就被称为一种 \textsc{AI} 语言. 这一点在 1980 年代 \textsc{AI} 短暂崛起时又被多次提起,
+已经差不多成了惯例.
+
+幸运的是, 关于 \textsc{AI} 并非 Lisp 全部内容的观点已经传开了. 近年来软硬件的优势已经使 Lisp 具有了商业活力:
+它目前用于 GNU Emacs, Unix 下最好的文本编辑器; AutoCAD, 工业标准桌面 \textsc{CAD} 程序; 还有 Interleaf,
+领先的高端出版系统. Lisp 在这些程序里的应用跟 \textsc{AI} 已经没有任何关系.
+
+如果 Lisp 不是一种 \textsc{AI} 语言, 那它是什么? 与其衡量 Lisp 在那些使用它的公司里的应用,
+我们不如直接看看语言本身. 什么是你可以用 Lisp 做到而其他语言做不到的呢?
+Lisp 的一个最显著的特征是可以根据用它写的程序来量身定做. Lisp 本身就是一个 Lisp 程序, Lisp 程序可以表达成列表,
+那也是 Lisp 的数据结构. 总之, 这两个原则意味着任何用户都可以为 Lisp 增加新操作符,
+而在这些操作符和那些内置的之间是不可区分的.
+
+\section{渐进式设计}
+\label{sec:design_by_evolution}
+
+由于 Lisp 给了你自定义操作符的自由, 你就可以随意地将它变成你所需要的语言. 如果你在写一个文本编辑器, 那么可以把
+Lisp 转换成专用于写文本编辑器的语言. 如果你在编写 CAD 程序, 那么可以把 Lisp 转换成专用于写 CAD 程序的语言.
+并且如果你还不太清楚你要写哪种程序, 那么用 Lisp 来写会比较安全. 无论你想写哪种程序, 在你写的时候, Lisp
+都可以演变成用于写 \textsl{那} 种程序的语言.
+
+如果你还不太确定你要写哪种程序? 对有些人来说,这句话已经是老一套了\footnote{感谢 88:barbieQ
+同学提供该句的专业翻译, 原文是``To some ears that sentence has an odd ring to it.}.
+这与特定的做事模式有巨大差异: 在特定的做事模式中, (1) 仔细计划你打算做的事情, (2) 去执行它.
+在这种模式下, 如果 Lisp 鼓励你在决定程序应该如何工作之前就开始写程序, 它只不过是鼓励了草率的思考而已.
+
+那么, 事情不是这样的. 先策划再实现的方法可能是建造水坝或者发起袭击的方式, 但经验并未显示这也是一种写程序的良好方式.
+为什么? 也许是因为计算机要求得太苛刻了. 也许是因为程序之中包含比水坝或者袭击更多的变数.
+或者也许老方法不再奏效是因为旧式的冗余观念不适合于软件开发: 如果一个大坝包含有额外 $30\%$ 的混凝土,
+那么可能处在一个错误的边缘, 但如果一个程序多做了额外 $30\%$ 的工作, 那 \textsl{就是} 一个错误.
+
+很难说清楚为何旧的方法失效了, 但一旦真的失效, 任何人都能看到. 究竟什么时候软件按时交付过?
+有经验的程序员知道无论你多小心地计划一个程序, 当你写它的时候, 之前制定的计划在某些地方就会变得不够完美.
+有时计划会毫无希望地错掉. 却很少有先策划再实施这一方法的受害者出来质疑它的有效性. 相反他们埋怨人的过失:
+只要计划做的更有前瞻性, 所有的麻烦就都可以避免. 由于即使最杰出的程序员在他们进行具体实现的时候也难免陷入麻烦,
+一味地要求人们必须具备那种程度的前瞻性可能有些过份了.
+也许这种先策划再实施的方法可以被另外一种更适合我们自身限制的观点取代.
+
+如果有合适的工具, 我们完全可以换一种角度看待编程. 为什么我们要在具体实现之前计划好一切呢?
+盲目开始一个项目的最大危险是我们可能不小心就使自己陷入困境. 但如果我们有一种更加灵活的语言, 这种担心能减轻一些吗?
+我们可以, 而且确实是这样. Lisp 的灵活性带来了全新的编程方式. 在 Lisp 中, 可以边写程序边做计划.
+
+为什么要等待事后聪明呢? 在 Montaigne 项目成立之初, 除了开始写以外几乎没什么明确的思路.
+一旦你可以从使自己陷入困境的危险中解脱出来, 那你就可以完全地驾驭这种可能性. 边设计边施工有两个重要的后果:
+程序可以花更少的时间去写, 因为当你把计划和实际动手写放在一起的时候, 你总可以集中精力在一个实际的程序上;
+然后让它变得越来越好, 因为最终的设计总是进化的产物. 只要在搜寻你程序的命运时维持一个确定的原则---
+只要你每当发现一个明确的错误时就直接把错误的部分重写掉---
+那么最终的产品可能比上手之前你花几个星期的时间精心设计的结果更加优雅.
+
+Lisp 的适应能力使这种编程思想成为可能. 确实, Lisp 的最大危险是它可能毁了你. 一旦你用过 Lisp 一段时间,
+你可能会对语言和应用程序之间的衔接过于敏感以至于你再能够回到另一种语言,
+那种你感觉并不能给你很多你所需要的灵活性的语言.
+
+\section{自底向上编程}
+\label{sec:programming_bottom-up}
+
+一个长期存在的编程原则是作为程序的功能性单元不应该过于庞大. 如果程序里某些组件的规模增长超过了它可读的程度,
+它就会成为可能隐藏错误的巨大复杂度来源, 就好像巨大的城市里的逃犯那样难以捉摸. 这样的软件将难以阅读, 难以测试,
+也难以调试.
+
+按照这一原则, 一个大型程序必须细分成小块, 并且越大规模的程序就应该分得越细. 但你怎样分割一个程序?
+传统的观点称为 \textsl{自顶向下设计}: 你说 ``该程序的目的是做这七件事, 那么我就把它分割成七个主要的子例程.
+第一个子例程要做这四件事, 所以它将进一步细分为它自己的四个子例程,'' 诸如此类.
+这一过程持续到整个程序被细分到合适的粒度---每一部分都足够大可以做一些实际的事情,
+但也足够小到可以作为一个基本单元来理解.
+
+有经验的 Lisp 程序员用另一种不同的方式来细化他们的程序. 类似自顶向下的设计那样, 他们根据一种称为
+\textsl{自底向上的设计} 原则来处理---通过改变语言来适应程序. 在 Lisp 中, 你不仅是根据语言向下写程序,
+也可以根据程序向上构造语言. 在你编程的时候你可能会想 ``我希望 Lisp 这样或者那样的操作符.''
+于是你就可以直接去实现它. 后来你意识到使用新的操作符可以简化程序中另一部分的设计, 诸如此类. 语言和程序一同演变.
+就像交战两国的边界一样, 语言和程序的界限不断地变化, 直到最终它们稳定在山川和河流的边缘,
+这也就是你要解决的问题本身的自然边界. 最后你的程序看起来就好像语言就是为解决它而设计的.
+并且当语言和程序都非常适应彼此时, 你得到的将是清晰, 短小和高效的代码.
+
+值得强调的是, 自底向上的设计并不意味着只是以不同的顺序写同一个程序. 当你以自底向上的方式工作时,
+你通常得到的是一个完全不同的程序. 你将得到一个带有更多抽象操作符的更大的语言, 和一个用它写的更小的程序,
+而不是一个简单的整块的程序. 你将得到一个拱而不是梁.
+
+在典型的代码中, 一旦把那些纯属 bookkeeping 的代码抽象出来, 剩下的东西就很短了; 你构造的语言越高阶,
+程序从上到下的距离就越短. 这带来了几点好处:
+
+\begin{enumerate}
+\item
+ 通过让语言做更多的工作, 自底向上设计产生的程序更加短小轻快. 一个更短小的程序就不必划分成许多的组件,
+ 并且更少的组件意味着程序更易于阅读和修改. 更少的组件也意味着更少的组件之间的连接, 这样也会减少错误发生的机会.
+ 一个工业设计者往往努力地减少一台机器上运动部件的数量, 同样有经验的 Lisp
+ 程序员使用自底向上的设计方法来减少他们程序的规模和复杂度.
+\item
+ 自底向上的设计促进了代码重用. 当你写两个或更多程序时, 许多你为第一个程序所写的工具也对后面的程序也有用.
+ 一旦你攒下来雄厚的工具基础, 写一个新程序所花的成本相比从裸 Lisp 环境开始可能只有几分之一.
+\item
+ 自底向上的设计提高了程序的可读性. 一个这种类型的抽象要求读者去理解一个通用操作符,
+ 而一个具体的函数抽象则要求读者去理解一个专用的子例程.
+\item
+ 由于自底向上的设计导致你总是去关注代码中的模式, 这种工作方式有助于澄清设计程序时的思路.
+ 如果一个程序中两个相距遥远的组件在形式上很相似, 你可能就会注意到这种相似性然后可能会以更简单的方式重新设计程序.
+\end{enumerate}
+
+自底向上的设计对于其他非 Lisp 语言来说也能达到一定程度. 当你看到库函数的时候, 那就是自底向上设计的结果.
+尽管如此, Lisp 带给你该领域更广阔的力量, 在 Lisp 风格程序中语言这一角色重要性也成比例地增加了---
+因此 Lisp 不仅是另一种不同的语言, 更是一整套完全不同的编程方式.
+
+可以认为这种开发风格更加适合那类基于小组开发的程序. 无论如何, 在相同的时间里, 它提高了一个小组所能做的事情的限制.
+在 \textsl{The Mythical Man--Month} 一书中, Frederick Brooks
+提出了一组程序员的生产力并不随人员数量线性增长的命题. 随着组内人数的提高, 个体程序员的生产力将有所下降.
+Lisp 编程经验以一种更加令人振奋的方式表明如下定律: 随着组内人数的减少, 个体程序员的生产力将有所提高.
+一个小组的成功, 相对而言, 仅仅是因为它规模小. 当一个小组开始运用 Lisp 所带来的技术优势时, 它毫无疑问地会走向成功.
+
+\section{可扩展软件}
+\label{sec:extensible_software}
+
+随着软件复杂性的提高, Lisp 编程风格的重要性正在逐步提高.专业用户现在对软件的要求如此之多以致于我们几乎不可能预见%
+到他们的所有需求. 但如果我们不能给他们一个能做到他们想要的每一件事的软件, 我们可以给他们一个可扩展的软件.
+我们把自己的软件从仅是一个程序转化成一个编程语言, 然后高级用户就可以在此基础上构建他们需要的额外特性.
+
+自底向上的设计很自然地产生了可扩展的程序. 最简单的自底向上程序包括两层: 语言和程序. 复杂程序可以被写成一系列层,
+每一层作为其上层的编程语言. 如果这一哲学被一直用到最上面的那层, 那这层对于用户来说就变成了一门编程语言.
+这样一个可扩展性渗透到每一层次的程序, 相比那些先按照传统黑箱方法写成,
+然后又想得到可扩展性的那些系统来说, 可以成为一个好得多的编程语言.
+
+X Window 和 \TeX 是基于此设计原则的早期实例. 在 1980 年代, 更强大的硬件使那些将 Lisp
+作为扩展语言的新一代的程序成为可能. 首先是 GNU Emacs, 流行的 Unix 文本编辑器. 紧接着是 AutoCAD,
+第一个提供 Lisp 作为其扩展语言的大规模商业产品. 1991 年 Interleaf 发布了他们软件的新版本, 它不仅采用
+Lisp 作为扩展语言, 甚至该软件大部分是用 Lisp 实现的.
+
+Lisp 是一个用来编写可扩展程序的特别好的语言, 主要是因为它本身就是一个可扩展的程序. 如果你用 Lisp
+写你的程序以便将这种可扩展性传递到用户那里, 你事实上已经毫不费力地得到了一个可扩展语言. 并且用 Lisp
+扩展一个 Lisp 程序, 和用一个传统语言做同样的事情相比, 它们的区别就好像面对面和使用书信跟人交谈那样.
+在一个以简单地提供访问外部程序的方式来达到可扩展性的程序里, 我们能期待的最好的事情也无非是两个黑箱之间彼此%
+通过预先定义好的频道进行通信. 在 Lisp 里, 这些扩展有权限直接访问整个底层程序. 这并不是说你给了用户访问你%
+程序中每一个部分的权限---只是说你现在至少有 \textsl{机会} 决定是否给他们这样的权限.
+
+当这种程序的权限和交互式环境组合在一起的时候, 你就拥有了发挥到极致的可扩展性. 任何你想用来作为扩展基础的软件,
+在你心里就好比有了一个巨大, 非常大的完整的蓝图. 如果其中的某些东西不敢确定怎么办? 如果原始程序是用 Lisp
+开发的, 那就可以交互式地试探它: 你可以检查它的数据结构; 你可以调用它的函数; 你可能甚至可以去看它最初的源代码.
+这种反馈信息允许你以很高的置信度来写程序---去写更加雄心勃勃的扩展, 并且让他们更快.
+交互式环境总是可以使编程更加简单, 但它对写扩展的人来说无疑更具价值.
+
+可扩展的程序是一把双刃剑, 但近来的经验表明, 相比一把钝剑来说用户更喜欢双刃剑. 可扩展的程序看起来正在流行,
+不论它们是否暗含危险.
+
+\section{扩展 Lisp}
+\label{sec:extending_lisp}
+
+有两种方式可以给 Lisp 增加新操作符: 函数和宏. 在 Lisp 里, 你定义的函数和那些内置函数具有相同的状态.
+如果你想要一个新变种的 \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}
+
+定义函数相对来说比较清楚明白. 宏提供了一种更通用, 但不太容易理解的定义新操作符的手段. 宏是用来写程序的程序.
+这句话意味深长, 深入地探究这个问题正是本书的主要目的.
+
+考虑周到地使用宏, 可以使程序令人惊叹地清晰简洁. 这些好处绝非唾手可得. 尽管最后宏将被视为世界上最自然的东西,
+但最初理解它们的时候却非常艰难. 部分原因是它们比函数更加一般化, 所以书写它们的时候要考虑的事情更多.
+但宏难于理解的最主要原因是它们属 \textsl{外来} 事物. 没有任何其他语言有像 Lisp 宏那样的东西.
+所以学习宏可能必须要从头脑中清除从其他语言那里偶然学到的先入为主的观念. 其中最重要的就是程序这一概念要完全颠覆了.
+为什么数据结构是流动的并且可改变, 而程序却不能呢? 在 Lisp 中, 程序就是数据,
+但这一事实的深层含义需要花一些时间才能体会到.
+
+如果需要花一些时间才能会用宏, 那它最好值得这样做. 即使像迭代这样平凡的用法中, 宏也可以使程序明显地变得小而清楚.
+假设一个程序需要在某个程序体上从 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} 对于我们可以用宏来做什么来说还只是个开始.
+
+你也并不限于每次只给 Lisp 扩展一个函数或者宏. 如果你需要, 就可以在 Lisp 之上构建一个完整的语言,
+然后用它来写你的程序. Lisp 对于写编译器和解释器来说是极为优秀的语言, 但它提供了定义新语言的另外一种方式,
+通常更加简洁并且当然也只需要更少的工作: 定义一种语言作为 Lisp 的变形. 然后 Lisp 中并未改变的部分可以在新语言里
+(例如算术或者 I/O) 继续被使用, 你只需要实现改变了的部分 (例如控制结构). 以这种方式实现的语言称为
+\textsl{嵌入式语言}.
+
+嵌入式语言是自底向上程序设计的自然产物. Common Lisp 里已经包括几种. 其中最著名的, \textsc{CLOS},
+将在最后一章里讨论. 但你也可以定义自己的嵌入式语言. 你可以得到一个完全符合你程序需要的语言,
+甚至它们最后看起来跟 Lisp 已经非常不同.
+
+\section{为什么 (或者说何时) 用 Lisp}
+\label{sec:why_lisp}
+
+这些新的可能性并非根植于简单的魔法成分. 在这种观点下, Lisp 就像一个拱顶. 究竟哪一块楔形石头 (拱石)
+托起了整个拱呢? 这个问题本身就是错误的; 每一块都是. 如同拱一样, Lisp 是一系列联锁特性的集合.
+我们可以列出这些特性中的一部分---动态存储分配和垃圾收集, 运行时类型, 函数对象, 生成列表的内置解析器,
+一个接受列表形式的程序的编译器, 交互式环境等等---但 Lisp 的威力不能归功于它们中的任何一种.
+是上述这些特性的组合铸就了 Lisp 编程目前的样子.
+
+过去的二十年里, 人们编程的方式变了. 某些变化---交互式环境, 动态链接, 甚至面向对象程序设计---
+已被零碎地尝试用于其他语言以得到 Lisp 那样的灵活性. 关于拱顶的那个比喻说明了这些语言是如何成功的.
+
+众所周知, Lisp 和 Fortran 是当前仍在使用中的两门最古老的编程语言.
+可能最关键的问题在于它们在语言设计的哲学中体现出截然相反的两种极端. Fortran
+被发明出来作为汇编语言的增强. Lisp 被发明出来用于表达算法. 如此截然不同的意图产生了差异极大的不同语言.
+Fortran 使编译器作者的生活更轻松; 而 Lisp 则使程序员的生活更轻松. 自从那时起,
+大多数编程语言都落在了两极之间. Fortran 和 Lisp 已经也使它们逐渐离中央地带更近. Fortran 现在看起来像
+Algol, 而 Lisp 已经放弃了它年幼时的一些很浪费的语言习惯.
+
+最初的 Fortran 和 Lisp 在某种程度上定义了一个战场. 战场的一边的口号是 ``效率! (并且另外还包括几乎不可能实现.)''
+在战场的另一边, 口号是 ``抽象! (并且不管怎么说, 这不是产品级软件.)'' 就好像上帝从远处决定古希腊战争的结果那样,
+编程语言这场战争的结果取决于硬件. 每一年看起来都对 Lisp 更有利. 现在针对 Lisp 的争论听起来已经有点儿像
+1970 年代早期汇编语言程序员对高级语言说的那样. 问题不再是 \textsl{为什么用 Lisp?}, 而是
+\textsl{何时用 Lisp?}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
Added: books/onlisp/2-functions.tex
==============================================================================
--- (empty file)
+++ books/onlisp/2-functions.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,10 @@
+\chapter{函数}
+\label{chap:functions}
+
+函数是 Lisp 程序的构造单元. 它们也是 Lisp 的构造单元.
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
Added: books/onlisp/24-prolog.tex
==============================================================================
--- (empty file)
+++ books/onlisp/24-prolog.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,8 @@
+\chapter{Prolog}
+\label{chap:prolog}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
Added: books/onlisp/3-functional_programming.tex
==============================================================================
--- (empty file)
+++ books/onlisp/3-functional_programming.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,8 @@
+\chapter{函数型编程}
+\label{chap:functional_programming}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
Added: books/onlisp/notes.tex
==============================================================================
--- (empty file)
+++ books/onlisp/notes.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,8 @@
+\chapter*{Notes}
+\label{chap:notes}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
Added: books/onlisp/onlisp.kilepr
==============================================================================
--- (empty file)
+++ books/onlisp/onlisp.kilepr Mon Aug 27 22:13:01 2007
@@ -0,0 +1,32 @@
+[General]
+img_extIsRegExp=false
+img_extensions=.eps .pdf .dvi .ps .fig .gif .jpg .jpeg .png
+kileprversion=1
+kileversion=1.9.3
+lastDocument=onlisp.tex
+masterDocument=
+name=onlisp
+pkg_extIsRegExp=false
+pkg_extensions=.cls .sty .dtx
+src_extIsRegExp=false
+src_extensions=.tex .ltx .bib .mp
+
+[Tools]
+MakeIndex=
+QuickBuild=
+
+[item:onlisp.kilepr]
+archive=true
+column=148039600
+encoding=
+highlight=
+line=0
+open=false
+
+[item:onlisp.tex]
+archive=true
+column=35
+encoding=UTF-8
+highlight=LaTeX
+line=11
+open=true
Added: books/onlisp/onlisp.tex
==============================================================================
--- (empty file)
+++ books/onlisp/onlisp.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,34 @@
+\documentclass[a4paper,10pt]{book}
+\usepackage{CJK}
+\usepackage{indentfirst}
+
+\begin{document}
+\begin{CJK}{UTF8}{song}
+\CJKindent
+
+\begin{titlepage}
+\title{On Lisp\footnote{原书地址: \texttt{http://www.paulgraham.com/onlisp.html}}}
+\author{Paul Graham 著
+ \and Chun Tian (binghe) 译\footnote{\texttt{E-mail: binghe.lisp(a)gmail.com}}}
+\end{titlepage}
+
+\maketitle
+\include{0-preface}
+\tableofcontents
+\setcounter{page}{0}
+\include{1-the_extensible_language}
+\include{2-functions}
+\include{3-functional_programming}
+\include{24-prolog}
+
+\include{packages}
+\include{notes}
+
+\end{CJK}
+\end{document}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: t
+%%% End:
Added: books/onlisp/packages.tex
==============================================================================
--- (empty file)
+++ books/onlisp/packages.tex Mon Aug 27 22:13:01 2007
@@ -0,0 +1,8 @@
+\chapter*{Appendix: Packages}
+\label{chap:packages}
+
+%%% Local Variables:
+%%% coding: utf-8
+%%% mode: latex
+%%% TeX-master: nil
+%%% End:
1
0