博客园  :: 首页  :: 联系 :: 管理

lisp的本质[5]

Posted on 2011-08-18 10:52  雪庭  阅读(452)  评论(0)    收藏  举报

你好, Lisp

Hello, Lisp!

 
Everything we've learned about Lisp so far can be summarized by a single
statement: Lisp is executable XML with a friendlier syntax. We haven't said a
single word about how Lisp actually operates. It's time to fill this gap2.

到此刻为止, 我们所知的关于Lisp的指示可以总结为一句话: Lisp是一个可执行的语法更
优美的XML, 但我们还没有说Lisp是怎样做到这一点的, 现在开始补上这个话题。
 
Lisp has a number of built in data types. Integers and strings, for example,
aren't much different from what you're used to. The meaning of 71 or "hello" is
roughly the same in Lisp as in C++ or Java. What is of more interest to us are
symbols, lists, and functions. I will spend the rest of this section describing
these data types as well as how a Lisp environment compiles and executes the
source code you type into it (this is called evaluation in Lisp lingo). Getting
through this section in one piece is important for understanding true potential
of Lisp's metaprogramming, the unity of code and data, and the notion of domain
specific languages. Don't think of this section as a chore though, I'll try to
make it fun and accessible. Hopefully you can pick up a few interesting ideas on
the way. Ok. Let's start with Lisp's symbols.

Lisp有丰富的内置数据类型, 其中的整数和字符串和其他语言没什么分别。像71或者
"hello"这样的值, 含义也和C++或者Java这样的语言大体相同。真正有意思的三种类型是
符号(symbol), 表和函数。这一章的剩余部分, 我都会用来介绍这几种类型, 还要介绍
Lisp环境是怎样编译和运行源码的。这个过程用Lisp的术语来说通常叫做求值。通读这一
节内容, 对于透彻理解元编程的真正潜力, 以及代码和数据的同一性, 和面向领域语言的
观念, 都极其重要。万勿等闲视之。我会尽量讲得生动有趣一些, 也希望你能获得一些
启发。那好, 我们先讲符号。
 
A symbol in Lisp is roughly equivalent to C++ or Java's notion of an
identifier. It's a name you can use to access a variable (like currentTime,
arrayCount, n, etc.) The difference is that a symbol in Lisp is a lot more
liberal than its mainstream identifier alternative. In C++ or Java you're
limited to alphanumeric characters and an underscore. In Lisp, you are not. For
example + is a valid symbol. So is -, =, hello-world, hello+world, *, etc. (you
can find the exact definition of valid Lisp symbols online). You can assign to
these symbols any data-type you like. Let's ignore Lisp syntax and use
pseudo-code for now. Assume that a function set assigns some value to a symbol
(like = does in Java or C++). The following are all valid examples:

大体上, 符号相当于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"

At this point something must smell wrong. If we can assign strings and integers
to symbols like *, how does Lisp do multiplication? After all, * means multiply,
right? The answer is pretty simple. Functions in Lisp aren't special. There is a
data-type, function, just like integer and string, that you assign to symbols. A
multiplication function is built into Lisp and is assigned to a symbol *. You
can reassign a different value to * and you'd lose the multiplication
function. Or you can store the value of the function in some other
variable. Again, using pseudo-code:
 
好像有什么不对的地方? 假定我们对*赋给整数或者字符串值, 那做乘法时怎么办? 不管
怎么说, *总是乘法呀? 答案简单极了。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

You can even do wacky stuff like reassigning plus to minus:
 
再古怪一点, 把减号的值赋给加号:
 
    set(+, -)       // 减号(-)是内置的减法函数
    +(5, 4)         // 加号(+)现在是代表减法函数, 结果是5减4等于1
 
I've used functions quite liberally in these examples but I didn't describe them
yet. A function in Lisp is just a data-type like an integer, a string, or a
symbol. A function doesn't have a notion of a name like in Java or C++. Instead,
it stands on its own. Effectively it is a pointer to a block of code along with
some information (like a number of parameters it accepts). You only give the
function a name by assigning it to a symbol, just like you assign an integer or
a string. You can create a function by using a built in function for creating
functions, assigned to a symbol 'fn'. Using pseudo-code:

这只是举例子, 我还没有详细讲函数。Lisp中的函数是一种数据类型, 和整数, 字符串,
符号等等一样。一个函数并不必然有一个名字, 这和C++或者Java语言的情形很不相同。
在这里函数自己代表自己。事实上它是一个指向代码块的指针, 附带有一些其他信息(例
如一组参数变量)。只有在把函数赋予其他符号时, 它才具有了名字, 就像把一个数值或
字符串赋予变量一样的道理。你可以用一个内置的专门用于创建函数的函数来创建函数,
然后把它赋值给符号fn, 用伪码来表示就是:
 
    fn [a]
    {
        return *(a, 2);
    }

This returns a function that takes a single parameter named 'a' and doubles
it. Note that the function has no name but you can assign it to a symbol:
 
这段代码返回一个具有一个参数的函数, 函数的功能是计算参数乘2的结果。这个函数还
没有名字, 你可以把此函数赋值给别的符号:
 
    set(times-two, fn [a] {return *(a, 2)})

We can now call this function:
 
我们现在可以这样调用这个函数:
 
    time-two(5)         // 返回10

Now that we went over symbols and functions, what about lists? Well, you already
know a lot about them. Lists are simply pieces of XML written in s-expression
form. A list is specified by parentheses and contains Lisp data-types (including
other lists) separated by a space. For example (this is real Lisp, note that we
use semicolons for comments now):
 
我们先跳过符号和函数, 讲一讲表。什么是表? 你也许已经听过好多相关的说法。表, 一
言以蔽之, 就是把类似XML那样的数据块, 用s表达式来表示。表用一对括号括住, 表中元
素以空格分隔, 表可以嵌套。例如(这回我们用真正的Lisp语法, 注意用分号表示注释):
 
    ()                      ; 空表
    (1)                     ; 含一个元素的表
    (1 "test")              ; 两元素表, 一个元素是整数1, 另一个是字符串
    (test "hello")          ; 两元素表, 一个元素是符号, 另一个是字符串
    (test (1 2) "hello")    ; 三元素表, 一个符号test, 一个含有两个元素1和2的
                            ; 表, 最后一个元素是字符串

When a Lisp system encounters lists in the source code it acts exactly like Ant
does when it encounters XML - it attempts to execute them. In fact, Lisp source
code is only specified using lists, just like Ant source code is only specified
using XML. Lisp executes lists in the following manner. The first element of the
list is treated as the name of a function. The rest of the elements are treated
as functions parameters. If one of the parameters is another list it is executed
using the same principles and the result is passed as a parameter to the
original function. That's it. We can write real code now:
 
当Lisp系统遇到这样的表时, 它所做的, 和Ant处理XML数据所做的, 非常相似, 那就是试
图执行它们。其实, Lisp源码就是特定的一种表, 好比Ant源码是一种特定的XML一样。
Lisp执行表的顺序是这样的, 表的第一个元素当作函数, 其他元素当作函数的参数。如果
其中某个参数也是表, 那就按照同样的原则对这个表求值, 结果再传递给最初的函数作为
参数。这就是基本原则。我们看一下真正的代码:


(* 3 4)                 ; equivalent to pseudo-code *(3, 4).
                        ; Symbol '*' is a function
                        ; 3 and 4 are its parameters.
                        ; Returns 12.
(times-two 5)           ; returns 10
(3 4)                   ; error: 3 is not a function
(times-two)              ; error, times-two expects one parameter
(times-two 3 4)          ; error, times-two expects one parameter
(set + -)               ; sets symbol '+' to be equal to whatever symbol '-'
                        ; equals to, which is a minus function
(+ 5 4)                 ; returns 1 since symbol '+' is now equal
                        ; to the minus function
(* 3 (* 2 2))           ; multiplies 3 by the second parameter
                        ; (which is a function call that returns 4).
                        ; Returns 12.
 
(* 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


Note that so far every list we've specified was treated by a Lisp system as
code. But how can we treat a list as data? Again, imagine an Ant task that
accepts XML as one of its parameters. In Lisp we do this using a quote operator
' like so:
 
上述的例子中, 所有的表都是当作代码来处理的。怎样把表当作数据来处理呢? 同样的,
设想一下, Ant是把XML数据当作自己的参数。在Lisp中, 我们给表加一个前缀'来表示数
据。

(set test '(1 2))       ; test is equal to a list of two integers, 1 and 2
(set test (1 2))        ; error, 1 is not a function
(set test '(* 3 4))     ; sets test to a list of three elements,
                        ; a symbol *, an integer 3, and an integer 4
 
(set test '(1 2))       ; test的值为两元素表
(set test (1 2))        ; 错误, 1不是函数
(set test '(* 3 4))     ; test的值是三元素表, 三个元素分别是*, 3, 4

We can use a built in function head to return the first element of the list, and
a built in function tail to return the rest of the list's elements:
 
我们可以用一个内置的函数head来返回表的第一个元素, tail函数来返回剩余元素组成的
表。

(head '(* 3 4))         ; returns a symbol '*'
(tail '(* 3 4))         ; returns a list (3 4)
(head (tail '( * 3 4))) ; (tail '(* 3 4)) returns a list (3 4)
                        ; and (head '(3 4)) returns 3.
(head test)             ; test was set to a list in previous example
                        ; returns a symbol '*'
 
(head '(* 3 4))         ; 返回符号*
(tail '(* 3 4))         ; 返回表(3 4)
(head (tal '(* 3 4)))   ; 返回3
(head test)             ; 返回*

You can think of built in Lisp functions as you think of Ant tasks. The
difference is that we don't have to extend Lisp in another language (although we
can), we can extend it in Lisp itself as we did with the times-two example. Lisp
comes with a very compact set of built in functions - the necessary minimum. The
rest of the language is implemented as a standard library in Lisp itself.
 
你可以把Lisp的内置函数想像成Ant的任务。差别在于, 我们不用在另外的语言中扩展
Lisp(虽然完全可以做得到), 我们可以用Lisp自己来扩展自己, 就像上面举的times-two
函数的例子。Lisp的内置函数集十分精简, 只包含了十分必要的部分。剩下的函数都是作
为标准库来实现的。