Lisp的不同之处
Lisp的不同之处
2001年12月(修订于2002年5月)
(本文是回应LL1邮件列表上一些问题而产生的。现在已收录在《书呆子的复仇》中。)
当McCarthy在1950年代末设计Lisp时,它是对现有语言的彻底突破,其中最重要的是Fortran。
Lisp体现了九个新思想:
-
条件语句。 条件语句是if-then-else结构。现在我们认为这是理所当然的。它们是McCarthy在开发Lisp过程中发明的。(当时的Fortran只有条件goto,紧密基于底层硬件的分支指令。)McCarthy是Algol委员会的成员,他将条件语句引入Algol,随后传播到大多数其他语言。
-
函数类型。 在Lisp中,函数是一等对象——它们是一种数据类型,就像整数、字符串等,并且有字面表示,可以存储在变量中,可以作为参数传递,等等。
-
递归。 递归当然在Lisp之前作为数学概念存在,但Lisp是第一个支持递归的编程语言。(可以说这在使函数成为一等对象中是隐含的。)
-
变量的新概念。 在Lisp中,所有变量实际上都是指针。具有类型的是值,而不是变量,赋值或绑定变量意味着复制指针,而不是它们指向的内容。
-
垃圾回收。
-
由表达式组成的程序。 Lisp程序是表达式的树,每个表达式都返回一个值。(在某些Lisp中,表达式可以返回多个值。)这与Fortran和大多数后续语言形成对比,后者区分表达式和语句。
在Fortran中有这种区别是很自然的,因为(在输入格式为打孔卡的语言中不足为奇)该语言是面向行的。你不能嵌套语句。因此,虽然你需要表达式来进行数学运算,但让其他任何东西返回值都没有意义,因为不可能有任何东西在等待它。
随着块结构语言的出现,这个限制消失了,但那时已经太晚了。表达式和语句的区别已经根深蒂固。它从Fortran传播到Algol,然后传播到它们的后代。
当一种语言完全由表达式构成时,你可以任意组合表达式。你可以说(使用Arc语法)
(if foo (= x 1) (= x 2))
或者
(= x (if foo 1 2))
-
符号类型。 符号与字符串的不同在于你可以通过比较指针来测试相等性。
-
使用符号树表示代码的记号。
-
整个语言始终可用。 读取时间、编译时间和运行时间之间没有真正的区别。你可以在读取时编译或运行代码,在编译时读取或运行代码,在运行时读取或编译代码。
在读取时运行代码允许用户重新编程Lisp的语法;在编译时运行代码是宏的基础;在运行时编译是Lisp用作扩展语言的基础,如在Emacs等程序中;在运行时读取使程序能够使用s表达式进行通信,这个想法最近被重新发明为XML。
当Lisp首次发明时,所有这些想法都与当时的普通编程实践相去甚远,后者主要由1950年代末可用的硬件决定。
随着时间的推移,体现在一系列流行语言中的默认语言逐渐向Lisp演变。1-5现在已经广泛传播。6开始出现在主流中。Python具有7的一种形式,尽管似乎没有任何语法支持它。8(与9一起)是使Lisp宏成为可能的特性,到目前为止仍然是Lisp独有的,也许是因为(a)它需要那些括号,或者同样糟糕的东西,(b)如果你添加这最后的力量增量,你不能再声称发明了一种新语言,而只是设计了一种新的Lisp方言 ;-)
虽然对当今的程序员有用,但通过描述Lisp与其他语言采用的随机权宜之计的差异来描述它是很奇怪的。这可能不是McCarthy对它的看法。Lisp不是设计来修复Fortran中的错误;它更像是试图将计算公理化的副产品。
相关链接:
What Made Lisp Different
December 2001 (rev. May 2002)
(This article came about in response to some questions on the LL1 mailing list. It is now incorporated in Revenge of the Nerds.)
When McCarthy designed Lisp in the late 1950s, it was a radical departure from existing languages, the most important of which was Fortran.
Lisp embodied nine new ideas:
-
Conditionals. A conditional is an if-then-else construct. We take these for granted now. They were invented by McCarthy in the course of developing Lisp. (Fortran at that time only had a conditional goto, closely based on the branch instruction in the underlying hardware.) McCarthy, who was on the Algol committee, got conditionals into Algol, whence they spread to most other languages.
-
A function type. In Lisp, functions are first class objects— they’re a data type just like integers, strings, etc, and have a literal representation, can be stored in variables, can be passed as arguments, and so on.
-
Recursion. Recursion existed as a mathematical concept before Lisp of course, but Lisp was the first programming language to support it. (It’s arguably implicit in making functions first class objects.)
-
A new concept of variables. In Lisp, all variables are effectively pointers. Values are what have types, not variables, and assigning or binding variables means copying pointers, not what they point to.
-
Garbage-collection.
-
Programs composed of expressions. Lisp programs are trees of expressions, each of which returns a value. (In some Lisps expressions can return multiple values.) This is in contrast to Fortran and most succeeding languages, which distinguish between expressions and statements.
It was natural to have this distinction in Fortran because (not surprisingly in a language where the input format was punched cards) the language was line-oriented. You could not nest statements. And so while you needed expressions for math to work, there was no point in making anything else return a value, because there could not be anything waiting for it.
This limitation went away with the arrival of block-structured languages, but by then it was too late. The distinction between expressions and statements was entrenched. It spread from Fortran into Algol and thence to both their descendants.
When a language is made entirely of expressions, you can compose expressions however you want. You can say either (using Arc syntax)
(if foo (= x 1) (= x 2))
or
(= x (if foo 1 2))
-
A symbol type. Symbols differ from strings in that you can test equality by comparing a pointer.
-
A notation for code using trees of symbols.
-
The whole language always available. There is no real distinction between read-time, compile-time, and runtime. You can compile or run code while reading, read or run code while compiling, and read or compile code at runtime.
Running code at read-time lets users reprogram Lisp’s syntax; running code at compile-time is the basis of macros; compiling at runtime is the basis of Lisp’s use as an extension language in programs like Emacs; and reading at runtime enables programs to communicate using s-expressions, an idea recently reinvented as XML.
When Lisp was first invented, all these ideas were far removed from ordinary programming practice, which was dictated largely by the hardware available in the late 1950s.
Over time, the default language, embodied in a succession of popular languages, has gradually evolved toward Lisp. 1-5 are now widespread. 6 is starting to appear in the mainstream. Python has a form of 7, though there doesn’t seem to be any syntax for it. 8, which (with 9) is what makes Lisp macros possible, is so far still unique to Lisp, perhaps because (a) it requires those parens, or something just as bad, and (b) if you add that final increment of power, you can no longer claim to have invented a new language, but only to have designed a new dialect of Lisp ;-)
Though useful to present-day programmers, it’s strange to describe Lisp in terms of its variation from the random expedients other languages adopted. That was not, probably, how McCarthy thought of it. Lisp wasn’t designed to fix the mistakes in Fortran; it came about more as the byproduct of an attempt to axiomatize computation.
Related: