LISP

编程语言
(重定向自Lisp

Lisp(歷史上拼寫為LISP),是具有悠久歷史的計算機編程語言家族,有獨特和完全用括號的前綴符號表示法[3]。起源於1958年[4],是現今第二悠久而仍廣泛使用的高階編程語言。只有FORTRAN編程語言比它更早一年[5][6]。Lisp編程語族已經演變出許多種方言。現代最著名的通用編程語種是SchemeCommon LispRacketClojure

Lisp
Lisp-glossy-120.jpg
编程范型多范型函数式过程式反射式元编程
設計者约翰·麦卡锡
實作者史帝芬·羅素, Timothy P. Hart和Mike Levin
发行时间1958年,​63年前​(1958
型態系統动态类型强类型
衍生副語言
Arc, AutoLISP, Clojure, Common Lisp, Emacs Lisp, ISLISP, newLISP, PicoLisp, Racket, Scheme, SKILL, T英语T (programming language)
啟發語言
IPL
影響語言
CLU, Dylan, Falcon, Forth, Haskell, Io, Ioke, JavaScript, Julia[1], Logo, Lua, LPC, MDL英语MDL (programming language), ML, Nu英语Nu (programming_language), OPS5英语OPS5, Perl, POP-2/11英语POP-11, Python, REBOL, Ruby, Smalltalk, Wolfram语言[2]

Lisp最初創建時受到阿隆佐·邱奇lambda演算的影響[7],用來作為計算機程序實用的數學表達[8]。因為是早期的高階編程語言之一,它很快成為人工智能研究中最受歡迎的編程語言[9]。在計算機科學領域,Lisp開創了許多先驅概念,包括:树结构自動記憶體管理动态类型条件表达式高階函數遞迴[10]、自主(self-hosting)編譯器[11]讀取﹣求值﹣輸出循環(REPL)[12]

"LISP"名稱源自「列表處理器」(英語:LISt Processor)的縮寫[13]列表是Lisp的主要數據結構之一,Lisp編程代碼也同樣由列表組成。因此,Lisp程序可以把源代碼當作數據結構進行操作,而使用其中的宏系統,開發人員可將自己定義的新語法或領域專用的語言,嵌入在Lisp編程中。

代碼和數據的可互換性為Lisp提供了立即可辨識的語法。所有的Lisp程序代碼都寫為S-表達式或以括號表示的列表。函數調用或語義形式也同樣寫成列表,首先是函數或操作符的名稱,然後接著是一或多個參數:例如,取三個參數的函數f即為(f arg1 arg2 arg3)

歷史编辑

1958年,約翰·麥卡錫麻省理工學院發明了Lisp程式語言。1960年,他在《ACM通讯》發表論文,名為《遞迴函數的符號表達式以及由機器運算的方式,第一部》[14]。在這篇論文中闡述了只要透過一些簡單的運算子,以及借鑒自阿隆佐·邱奇的用於匿名函數的表示法,就可以建立一個具圖靈完備性語言,可用於演算法中。

1955年至1956年間,資訊處理語言被創造出來用於人工智能。它首先使用的列表處理與遞歸概念被用於了Lisp。

麥卡錫最初使用M-表達式寫程式碼,之後再轉成S-表達式,舉例來說M-表达式英语M-expression的語法,car[cons[A,B]],等同於S-表達式(car (cons A B))。然而由於S-表達式具備同像性,即程式與資料由同樣的結構儲存,實際應用中一般只使用S-表達式,而棄用M-表達式。M-表達式曾出現在短暫存在的Horace Enea的MLisp英语MLisp[15]Vaughan Pratt英语Vaughan PrattCGOL英语CGOL之中。

約翰·麥卡錫的學生史帝芬·羅素在閱讀完此論文後,認為Lisp編程語言當中的eval函数可以用機器碼來實做。他在IBM 704機器上,寫出了第一個Lisp解释器[16]。1962年,Tim Hart與Mike Levin在麻省理工學院以Lisp編程語言,實做出第一個完整的Lisp編譯器[17]。這兩人在筆記中使用的語法比麥卡錫早期的代碼更接近現代Lisp風格。

研究生Daniel Edwards在1962年之前開發的垃圾收集程序,使得在通用計算機上運行Lisp變得實用,但效率仍然是一個問題。在1963年,Timothy Hart提议向Lisp 1.5增加[18]

在1975年,蓋伊·史提爾二世傑拉德·薩斯曼开发了Scheme,它是使用词法作用域尾调用优化的第一个Lisp方言[19]。在1980年代至1990年代期间,蓋伊·史提爾二世等人在将新的Lisp方言(多数是Maclisp的后继者比如ZetaLisp和NIL)统一成单一语言上进行了巨大的努力。新语言Common Lisp,在某种程度上兼容于它所替代的方言。在1994年,ANSI出版了Common Lisp标准《ANSI X3.226-1994信息技术编程语言Common Lisp》。此外还有嵌入到編輯器Emacs中的Emacs Lisp,它非常流行并建立了自己的标准。

联系于人工智能编辑

Lisp机器,现存于MIT博物馆英语MIT Museum

自从创始以来,Lisp就密切联系于人工智能研究社群,特别是在PDP-10系统之上[20]。在1970年傑拉德·薩斯曼特里·威诺格拉德使用Lisp实现了编程语言Micro-Planner英语Planner (programming language)[21],它被用在著名的AI系统SHRDLU之中。在1970年代,随着AI研究催生了商业分支,当时存在的Lisp系统的性能成为日益严重的问题。

使用20世紀70年代當時的編譯器技術和硬體,要實現Lisp還是困難的挑戰。這導致了專用Lisp機器的創建:用於運行Lisp環境和程序的專用硬體。之後計算機硬體和編譯器技術的發展迅速,使得昂貴的Lisp專用機器過時。

谱系和方言编辑

在它六十余年的历史中,Lisp产生了在S-表达式语言的核心主旨上的很多变体。此外,每个给定方言又可能有多种实现,例如Common Lisp就有十余种以上的实现。在一种标准化了的方言之内,符合标准的实现支持相同的核心语言,但是有着不同的扩展和函数库。

在方言间的差异可能是非常显眼的,例如定义一个函数,Common Lisp使用关键字defun[22],而Scheme使用define[23]Richard P. Gabriel英语Richard P. GabrielKent Pitman英语Kent Pitman在1998年的一篇论文中,按采用统一的还是分立的函数与值命名空间,将Lisp家族语言划分为Lisp1和Lisp2[24]

历史上的重要方言编辑

2000年迄今编辑

在1990年代衰退之後,Lisp在2000年之後因一些關注而逐漸復甦。當其他人認為Lisp已經是過時陳舊的,如保羅·格雷厄姆埃里克·雷蒙等人繼續出版有關於Lisp編程的著作,一些新的開發人員受到這些作者啟發,經常將Lisp這種語言描述為令人大開眼界的經驗,並聲稱在本質上比較其它編程語言更有生產效率[38]。這種意識的提高可對比於,Lisp在1990年代中期“人工智能冬季”的那種短暫增長[39]

在2007年出現了Clojure,它是一个Lisp的新近方言,编译至Java虚拟机并特别关注并发性。現今與Lisp有關的大多數新活動,都集中在SchemeCommon LispEmacs LispRacketClojure的實作上,包括開發新的跨平台函式庫和應用。Dan Weinreb在他的2010年調查中,列出了11個積極維護中的Common Lisp實作[40]

開源社群建立了至今仍活躍的支援基礎:CLiki英语CLiki是個收集Common Lisp相關資訊的維基,Planet Lisp收集了各種Lisp相關博客的內容,Common-lisp.net是開源專案的託管站點。Quicklisp則是含括了許多函式庫的裝載管理器。

Lisp50@OOPSLA慶祝了Lisp的50週年(1958-2008)[41]。在波士頓、溫哥華和漢堡,有定期的當地用戶會議。其他活動包括歐洲共同Lisp會議、歐洲Lisp專題討論會和國際Lisp研討會。

Scheme社群積極維護了二十多個實作。在2000年代開發了數個有意義的新實作(ChickenGambitGauche英语Gauche (Scheme implementation)Ikarus英语Ikarus (Scheme implementation)Larceny英语Larceny (Scheme implementation)Ypsilon英语Ypsilon (Scheme implementation)),Scheme社群廣泛接納了R5RS語言標準[42]。Scheme實作需求過程建立了很多準標準函式庫和Scheme擴展功能。各種 Scheme實作的用戶社群持續地增長。一個新的語言標準化過程於2003年開始,並在2007年產生了R6RS標準[43]。使用Scheme介紹計算機科學課程的學校似乎有所減少,麻省理工學院的計算機科學入門課程,已經不再使用Scheme[44][45]

近年来又有了幾種新的Lisp方言:ArcHy、Liskell[46]LFE英语LFE (programming language)。此外,Nu英语Nu (programming language)OS X上采用Lisp式语法的脚本语言;Chialisp是编译至CLVM的Chia区块链的链上编程环境。

在2019年10月,保羅·格雷厄姆发布了一个新的Lisp方言Bel[47]

Lisp編程語族時間軸编辑

主要方言编辑

Common LispScheme是Lisp发展的两大主流的代表。这些语言体现了显著不同的设计选择。

Common LispMaclisp的后继者。对它有重要影响的是Lisp Machine Lisp英语Lisp Machine Lisp、Maclisp、NIL英语NIL (programming language)S-1 Lisp英语S-1 LispSpice Lisp英语Spice Lisp和Scheme[48]。它拥有Lisp Machine Lisp(用于编程Lisp机器的大型Lisp方言)的很多特征,但设计上能高效的实现在任何个人计算机或工作站上。Common Lisp是通用编程语言,因而拥有一个大型语言标准,包括很多内建数据类型、函数、宏和其他语言元素,以及一个对象系统(Common Lisp对象系统)。Common Lisp还从Scheme借鉴了特定特征,比如词法作用域词法闭包。Common Lisp实现目标定为不同的平台,比如:LLVM[49]Java虚拟机[50]、x86-64、PowerPC、Alpha、ARM、Motorola 68000和MIPS[51],和不同的操作系统,比如:Windows、macOS、Linux、Solaris、FreeBSD、NetBSD、OpenBSD、Dragonfly BSD和Heroku[52]

Scheme是一个静态作用域和正当尾递归的Lisp编程语言方言[53],由Guy L. Steele, Jr.Gerald Jay Sussman发明。它的设计有着异常清晰和简单的语义,和很少的形成表达式的不同方式。它的设计大约比Common Lisp早上一个年代,Scheme是一个相当极简主义的设计。它拥有非常小的标准特征集合,但具有在Common Lisp中未规定的特定实现特征(比如尾递归优化和完全续体)。在Scheme中能方便的表达广阔的编程范型,包括指令式、函数式和消息传递风格。Scheme通过一系列的标准(第n次修订的算法语言Scheme报告)和一系列Scheme实现要求英语Scheme Requests for Implementation而持续的演化。

Clojure是Lisp的一个新近方言,其目标主要是Java虚拟机通用语言运行库(CLR)和编译成JavaScript。它被设计为一个务实的通用编程语言。Clojure受到了Haskell的相当大的影响,因而非常强烈的强调了不可变性[54]。Clojure提供了对Java框架和库的访问,具有可选的类型提示和类型推论,这样到Java的调用就可以避免反射并确使了快速的原始操作。Clojure设计上不后向兼容于其他Lisp方言[55]

进一步的,Lisp方言在很多应用中被用作脚本语言,其中最周知的是在Emacs编辑器中的Emacs Lisp,在AutoCAD中的AutoLISP和后来的Visual Lisp,Audacity中的Nyquist英语Nyquist (programming language),和LilyPondGNU Guile。有用的Scheme解释器潜在的有更小大小,使得它特别流行于嵌入式脚本。例子包括SIODTinyScheme,二者都曾经以共用名字“Script-fu”成功的嵌入到了GIMP图像处理软件中[56]。LIBREP是John Harper最初基于Emacs Lisp语言开发的Lisp解释器,它已经嵌入到了Sawfish窗口管理器[57]

标准化的方言编辑

Lisp有官方标准化和业界标准的方言:IEEE Scheme[58]ANSI Common Lisp、ISO ISLISPR6RS SchemeR7RS Scheme

语法和语义编辑

注意:本文的例子是用Common Lisp书写(但是其中的多数在Scheme中也是有效的)。

符号表达式(S-表达式)编辑

Lisp是一个面向表达式编程语言英语Expression-oriented programming language。不同于多数其他语言,在表达式和语句之间不做区分。所有代码和数据都写为表达式。当求值一个表达式的时候,它产生一个值(在Common Lisp中可能有多个值),它可以接着被嵌入到其他表达式中。每个值都可以是任何数据类型的。

McCarthy的1958年论文介入两种类型的语法:符号表达式(S-表达式或sexps),它镜像了代码和数据的内部表示;和元表达式(M-表达式英语M-expression),它表达S-表达式的函数。M-表达式从未得到青睐,几乎所有今天的Lisp都使用S-表达式来操纵代码和数据二者。

圆括号的使用是Lisp与其他编程语言家族最直接明显的差别。为此学生们一直将Lisp昵称为“迷失在愚蠢的括号中”(Lost In Stupid Parentheses)或“大量烦人的多余括号”(Lots of Irritating Superfluous Parentheses)[59]。但是S-表达式语法也承担了Lisp多数能力:语法极其正规,利于计算机操纵。然而Lisp的语法不局限于传统的括号表示法。它可以扩展为包括替代表示法。例如,XMLisp是Common Lisp扩展,它采用元对象协议来集成S-表达式和可扩展标记语言(XML)。

有赖于表达式给予了语言巨大的灵活性。由于Lisp函数都写为列表,它们可以完全就像数据那样处理。这允许了轻易的书写能操纵其他程序的程序(元编程)。很多Lisp方言使用宏系统来利用这个特征,它确使了语言扩展而几乎没有限制。

列表编辑

书写Lisp列表是以空白英语Whitespace character来分隔其元素的,并包围以圆括号。例如,(1 2 foo)是其元素为三个原子12foo的一个列表。这些值是隐含的有类型的:它们分别是两个整数和叫做“符号”的Lisp专有数据类型,但不需要显式的声明。

空列表()也表示为特殊原子nil。这是Lisp中既是原子又是列表的唯一实体。

表示式被写为使用前缀表示法的列表。在列表中第一个元素是一个函数的名字、一个宏的名字、一个lambda表达式或特殊算符的名字(见后)。列表的余下部份是实际参数。例如,函数list将它的实际参数作为一个列表返回,所以表达式:

 (list 1 2 (quote foo))

求值为列表(1 2 foo)。在前面例子中的foo之前的quote是特殊算符,它不求值就返回它的实际参数。任何未引述的表达式,在外围表达式被求值之前,都递归的被求值。例如:

 (list 1 2 (list 3 4))

求值为列表(1 2 (3 4))。注意第三个实际参数是一个列表;列表是可以嵌套的。

算符编辑

对算术算符的对待都是类似的。表达式:

 (+ 1 2 3 4)

求值为10。其在中缀表示法下的等价形式为"1 + 2 + 3 + 4"。

Lisp没有在Algol派生语言中实现的那样的算符概念。在Lisp中算术算符是可变参数函数(或多元函数),能够接受任何数目的实际参数。C-风格的++增加算符,有时在名字incf之下实现,给出的语法是:

 (incf x)

等价于(setq x (+ x 1)),它返回x的新值。

特殊算符(有时叫做特殊形式[60])提供了Lisp的控制结构。例如,特殊算符if接受三个实际参数。如果第一个实际参数为非nil,它求值为第二实际参数;否则它求值为第三个实际参数。因此表达式:

 (if nil
     (list 1 2 "foo")
     (list 3 4 "bar"))

求值为(3 4 "bar")。当然,如果在nil的位置替换上非平凡的表达式会更有用处。

Lisp还提供逻辑算符andornotandor算符进行短路求值,并分别返回它们的第一个nil或非nil实际参数:

 (or (and "zero" nil "never") "James" 'task 'time)

会求值为"James"

Lambda表达式和函数定义编辑

另一个特殊算符lambda,被用来绑定变量到值,接着对表达式求进行求值。这个算符还被用于建立函数:给lambda的实际参数是一个形式参数列表,和这个函数要求值的一个或多个表达式,它的返回值是其求值的最后一个表达式的值。表达式:

 (lambda (arg) (+ arg 1))

求值为一个函数,它接受一个实际参数,绑定它到arg并返回比这个实际参数大一的数。对待Lambda表达式于命名函数没有区别;它们以相同方式调用。如下表达式:

 ((lambda (arg) (+ arg 1)) 5)

求值为6。这里我们做了一次函数应用:我们通过传递给它值5而执行了这个匿名函数

命名函数是通过使用defun[22],将一个lambda表达式存储在一个符号之中而建立的:

 (defun foo (a b c d) (+ a b c d))

(defun f (a) b...)在全局环境中定义一个名为f的新函数。它在概念上类似于表达式:

 (setf (fdefinition 'f) #'(lambda (a) (block f b...)))

这里的setf是一个宏,用来设置第一个实际参数fdefinition 'f为一个新的函数对象。fdefinition对命名为f的函数的全局函数定义。#'是特殊算符function的简写,它返回一个函数对象

作用域和闭包编辑

Lisp家族按变量作用域分裂为两大支系,分别使用动态作用域[61],或使用静态(也叫做词法)作用域[62]SchemeCommon LispClojure缺省使用静态作用域,而AutoLISPPicoLisp[63]newLISP[64]使用动态作用域。Emacs Lisp自从Emacs版本24.1起使用动态和词法作用域二者[65]

原子编辑

在最初的LISP中,有两种基础数据类型:原子和列表。列表是元素的一个有限有序序列,这里的每个元素要么是一个原子要么是一个列表,而原子要么是一个要么是一个符号。符号实质上是唯一性命名的项目,在源代码中写为字母数字串,并被要么用作一个变量名字要么符号处理英语Computer algebra中的一个数据项目。例如,列表(FOO (BAR 1) 2)包含三个元素:符号FOO、列表(BAR 1)和数2

在原子和列表之间的本质区别是原子是不可变的和唯一性的。出现在源代码中不同位置的两个原子,如果以完全相同方式写成则表示相同的对象,而每个列表都是一个分立的对象,它们可以独立于其他列表而改变 ,并可以通过比较算符而区分于其他列表。

随着后来的Lisp介入了更多的数据类型,和编程风格的演化,原子的概念失去了重要性。很多方言出于遗产兼容性而保留了谓词atom,定义它为对不是cons的任何对象都为真。

cons和列表编辑

 
列表(42 69 613)的方框与指针示意图

Lisp列表被实现为单向链表[66]。这个链表的每个单元都叫做cons(在Scheme中叫做pair),它构成自两个指针,分别叫做car英语CAR and CDRcdr英语CAR and CDR

在众多可以用cons单元构建的数据结构中,最基本一个叫做“正当列表”(proper list)。正当列表要么是特殊的nil(空列表)符号,要么是一个cons,它的car指向一个数据项(它可以是另一个cons结构比如一个列表),而cdr指向另一个正当列表。

如果一个给定cons被接受为一个链表的头部,那么它的car指向这个列表的第一个元素,而它的cdr指向这个列表的余下部份。为此,在提及作为链表(而非树等)一部份的cons的时候,carcdr函数也分别叫做firstrest

因此,Lisp列表不是原子对象,它们类似C++或Java中的容器类的实例。一个列表就是链接的cons的一个聚集。引用一个给定列表的变量,简单的就是到列表中第一个cons的指针。遍历一个列表可以通过逐一cdr这个列表来进行,就是说连续的选取cdr来访问这个列表的每个cons;或者通过使用任何一个将一个函数映射在一个列表之上的高阶函数

由于cons和列表在Lisp系统中是普遍性的,经常有人误解它们是Lisp的唯一数据结构。事实上,除了最简单者之外的所有Lisp都有其他数据结构,比如向量(数组英语Array data type)、散列表、结构等等。

S-表达式表示列表编辑

圆括号的S-表达式表示了链表结构。有多种方式将相同的列表表示为一个S-表达式。cons可以用“点对表示法”写为(a . b),这里的acarbcdr。更长的正当列表可以用点对表示法写为(a . (b . (c . (d . nil))))。这通常简写为列表表示法的(a b c d)。一个非正当列表[67],可以用二者的组合来书写,比如列表(a b c . d)有三个cons,最后的cdrd(也就是完全特殊形式下的(a . (b . (c . d))))。

列表处理过程编辑

Lisp提供很多内建的过程,用来访问和控制列表。列表可以直接用list过程创建,它接受任何数目的实际参数,并返回这些实际参数的列表:

 (list 1 2 'a 3)
 ;Output: (1 2 a 3)

 (list 1 '(2 3) 4)
 ;Output: (1 (2 3) 4)

由于列表是从cons对构造而来,可以使用cons过程来在一个列表的前端增加一个元素。注意由于列表构造方法,cons在处理列表实际参数上是不对称的:

 (cons 1 '(2 3))
 ;Output: (1 2 3)
 
 (cons '(1 2) '(3 4))
 ;Output: ((1 2) 3 4)

append过程将两个(或更多)列表相互附加。由于Lisp列表是链表,附加两个列表有渐进时间复杂度  

 (append '(1 2) '(3 4))
 ;Output: (1 2 3 4)
 
 (append '(1 2 3) '() '(a) '(5 6))
 ;Output: (1 2 3 a 5 6)

共享结构编辑

Lisp列表,作为单向链表,可以相互共享结构。就是说,两个列表可以有相同的尾部,或者最终的cons序列。例如,在执行下列Common Lisp代码之后:

(setf foo (list 'a 'b 'c))
(setf bar (cons 'x (cdr foo)))

列表foobar分别是(a b c)(x b c)。然而,尾部(b c)在两个列表中都是相同的结构。它不是复件;对于两个列表指向bccons单元都在相同的内存位置。

共享结构而非复制可以得到相当客观的性能提升。但是这种技术可能以未预期的方式,交互于改变作为实际参数传递给它的列表的那些函数。改变一个列表,比如替代cgoose,将会影响另一个列表:

 (setf (third foo) 'goose)

这变更foo(a b goose),但是从而还变更了bar(x b goose),这是可能是未预期的结果。这是缺陷的来源,为此改变它们的实际参数的那些函数被文档标示为破坏性(destructive)的。

函数式编程爱好者避免破坏性函数。在青睐函数式风格的Scheme方言中,破坏性函数的名字都标记了警告性感叹号,或者叫做“bang”,比如set-car!(读作set car bang),它替换一个conscar。在Common Lisp方言中,破坏性函数是司空见惯的,与set-car!等价的是rplaca,它的名字表示“replace car”。这个函数是不常见的,因为Common Lisp包括了一个特殊设施setf,用来轻易的定义和使用破坏性函数。在Common Lisp中常见的风格是在构建原型的时候写函数式代码(没有破坏性调用),接着将破坏性调用作为优化增加于可以安全的进行它们的地方。

自求值形式和引述编辑

Lisp求值用户录入的表达式。符号和列表求值为某个其他(通常更简单的)表达式,例如:一个符号求值为它指名的变量的值;(+ 2 3)求值为5。但是,多数其他形式求值为其自身:如果录入5到Lisp中,它返回5

任何表达式都可以标记上防止被求值(对于符号和列表是需要的)。这是特殊算符quote的角色,它也简写为'(一个单引号)。例如,通常如果录入了符号foo,它返回对应变量的值(没有这个变量则为一个错误)。要引用这个文字英语Literal (computer programming)符号,录入要么(quote foo) ,要么更常见的'foo

Common Lisp和Scheme二者还支持“反引述”(backquote)算符(在Scheme中叫做准引述英语quasiquote),这时录入`字符(重音符)。它几乎同于普通引述,除了它允许表达式被求值,并将它们的值插入到引述列表之中,这些表达式标记了逗号,表示去引述,或逗号-at,@表示拼接算符。如果变量snue有值(bar baz),则`(foo ,snue)求值为(foo (bar baz)),而`(foo ,@snue)求值为(foo bar baz)。反引述经常用于定义宏展开[68][69]

自求值形式和引述形式是Lisp中文字英语Literal (computer programming)的等价者。在程序代码中可以修改(可变)文字的值。例如,如果一个函数返回一个引述形式,而调用这个函数的代码修改这个形式,这可以改变这个函数在后续调用时的行为:

(defun should-be-constant ()
  '(one two three))

(let ((stuff (should-be-constant)))
  (setf (third stuff) 'bizarre))   ; bad!

(should-be-constant)   ; returns (one two bizarre)

像这样修改一个引述形式通常被认为是不良风格,并且被ANSI Common Lisp定义为是危险的。它会导致在编译文件中的未定义的行为,因为文件编译器可以合并类似的常量并将它们放置到写保护内存中,等等。

Lisp的引述形式化已经被Douglas Hofstadter(在《Gödel, Escher, Bach》中)和其他人注解为自引用哲学想法的例子。

控制结构编辑

Lisp最初有很少的控制结构,但是在语言演化期间却增加了很多。Lisp的最初条件算符cond,是后来的if-then-else结构的先驱。

Scheme方言的编程者经常使用尾递归表达循环。Scheme在学术计算机科学中的通行性,导致了一些学生相信尾递归是在Lisp中书写迭代的唯一的或最常用的方式,但是这是不正确的。所有常见的Lisp方言都有指令式风格的迭代构造,从Scheme的do循环到Common Lisp的复杂的loop表达式。此外,使得这成为客观上而非主观上的一件事的关键要点,是Scheme对尾递归的处理提出了特殊要求,Scheme通常鼓励使用尾递归的原因,是语言定义明确的支持了这种实践。与之相反,ANSI Common Lisp不要求常称为尾递归消除的这种优化[70]。因此,不鼓励将尾递归风格作为使用更传统的迭代构造(比如dodolistloop)的替代品[71]。在Common Lisp中不只是风格偏好的问题,而是潜在的效率问题,因为在Common Lisp中明显的尾递归可能未被编译为简单的jump;和程序正确性问题,因为在Common Lisp中尾递归可能增加栈的使用而有堆栈溢出风险。

一些Lisp控制构造是特殊算符,等价于其他语言的语法关键字。使用这些算符的表达式与函数调用有着相同的表面外观,但是不同之处在于参数不是必须求值的,或者在迭代表达式的情况下,可以被求值多于一次。

对比于多数其他主要编程语言,Lisp允许使用语言自身实现控制结构。一些控制结构被实现为Lisp宏,想知道它们是如何工作的编程者甚至可以通过宏展开来研究。

Common Lisp和Scheme二者都有非局部控制流程算符。在这些算符中的不同是在这两种方言之间最深的差异。Scheme使用call/cc过程支持可重入的续体,它允许一个程序保存(并在将来恢复)执行中的特定位置。Common Lisp不支持可重入的续体,但是支持处理逃脱续体的一些方式。

相同的算法在Lisp中经常可以用要么指令式要么函数式风格来表达。如上所述,Scheme趋于青睐函数式风格,使用尾递归和续体来表达控制流程。但是,指令式风格仍是很有可能的。很多Common Lisp编程者偏好的风格,可能让使用结构化编程语言比如C的编程者看着更加熟悉,而Scheme编程者偏好的风格更加密切类似于纯函数式编程语言比如Haskell

由于Lisp在列表处理上的早期遗产,它拥有与在序列上迭代有关的一组广泛的高阶函数。在其他语言中需要显式循环(比如C中的for循环)的很多情况下,在Lisp和很多函数式编程语言中,相同的任务可以通过高阶函数来完成。

一个好的例子是在Scheme中叫做map而在Common Lisp中叫做mapcar的函数。给定一个函数和一个或多个列表,mapcar依次这个列表的元素之上连续的应用这个函数,并将结果收集入一个新的列表:

 (mapcar #'+ '(1 2 3 4 5) '(10 20 30 40 50))

这个mapcar函数将+函数应用于每个对应的元素对之上,产生结果(11 22 33 44 55)

程序代码的列表结构编辑

在Lisp和其他语言之间的基本区别是,在Lisp中一个程序的文本表示,简单的是人类可读的描述,它同于底层Lisp系统使用的内部数据结构(链表、符号、数、字符等)。

Lisp利用这一点来实现了一个非常强力的系统。就像其他宏语言比如C,一个宏返回可以接着被编译的代码。但是不同于C的宏,Lisp的宏是函数因而可以利用Lisp的全部能力。

进一步的,因为Lisp代码作为列表有着相同的结构,宏可以使用语言中任何列表处理函数来建造。简要的说,Lisp可以在数据结构上做的任何事情,Lisp宏都可以在代码上做。相反的,在多数其他语言中,解析器的输出是纯粹的内部于语言实现的而不能被编程者操纵。

这个特征可以在语言中开发高效语言。例如,Common Lisp对象系统可以使用宏清晰的实现为一个语言扩展。这意味着如果一个应用需要不同的继承机制,它可以使用不同的对象系统。这直接的对立于多数其他语言;例如,Java不能支持多重继承并且没有增加它的合理方式。

在简单的Lisp实现中,这个列表结构被直接的解释来运行这个程序;一个函数是在文字上的一段列表结构,它被解释器在执行它的时候遍历。但是,多数后来的Lisp系统还包括一个编译器。编译器将列表结构转换成机器代码或字节码用于执行。这个代码可以像用常规语言比如C编译的代码一样快速。

宏在编译步骤之前展开,因而提供一些有价值的选项。如果一个程序需要一个预先计算了的表格,那么一个宏可以在编译时间建立这个表格,所以编译器只需要输出这个表格,而不需要在运行时间调用代码来建立这个表格。一些Lisp实现甚至拥有一种eval-when机制,允许代码在编译时间期间出现(在一个宏需要它的时候),而不出现在发行的模块中[72]

求值和读取–求值–打印循环编辑

Lisp语言经常被以交互式命令行来使用,它还可以结合入集成开发环境(IDE)。用户在命令行录入表达式,或指示IDE将它们传送给Lisp系统。Lisp读取录入的表达式,求值它们,并打印结果。为此,Lisp命令行被叫做读取﹣求值﹣输出循环(REPL)。

REPL的基本操作描述如下。这是一个简化的描述,省略了很多真实Lisp的元素,比如引述和宏。

read函数接受文本的S-表达式作为输入,并将它们解析为内部数据结构。例如,如果你在提示符下录入文本(+ 1 2)read将它转换成有三个元素的一个链表:符号+、数1和数2。恰巧这个列表也是一段有效的Lisp代码,就是说它是可以被求值的。这是因为car这个列表指名了一个函数即加法运算。

注意foo将被读作一个单一符号。123将被读作数一百二十三。"123"将被读作字符串"123"

eval函数求值数据,返回零或多个其他Lisp数据作为结果。求值不必然意味着解释;一些Lisp系统编译所有的表达式为机器代码。但将求值描述为解释是很简单的:要求值其car指名一个函数的一个列表,eval首先求值在它的cdr中的每个实际参数,接着应用这个函数于这些实际参数。在这个案例中,这个函数是加法,而应用它于实际参数列表(1 2)产生答案3。这是这个求值的结果。

符号foo求值为符号foo的值。数据比如字符串"123"求值为相同的字符串。列表(quote (1 2 3))求值为列表(1 2 3)

print函数的工作是将输入表示给用户。对于简单结果比如3这是平凡的。求值为一段列表结构的一个表达式会要求print遍历这个列表并将结果输出为一个S-表达式。

要实现一个Lisp REPL,必需的只是实现这三个函数和一个无限循环函数。实现eval函数会很复杂是自然的,因为它必须也要实现所有特殊算符比如iflambda。它们完成后,一个基本的REPL就是一行代码:(loop (print (eval (read))))

Lisp REPL典型的也提供输入编辑、输入历史、错误处理和到调试器的接口。

Lisp通常使用及早求值。在Common Lisp中,实际参数以应用式次序(最左最内为先)求值,而在Scheme中实际参数的次序是未定义的,为编译器优化留下了余地[73]

範例编辑

Hello World!编辑

这个Hello World!例子展示Lisp的三种主要方言,在基本输出和函数定义上的用法:

Scheme Common Lisp Clojure
;; 显示过程在屏幕中打印字符串
;; 并返回未规定值
(display "Hello, world!")

;; 定义函数hello
(define (hello)
  (display "Hello, world!"))
 
;; 调用函数hello
(hello)
;; 格式化函数在第一个参数是t时, 
;; 在屏幕中打印字符串,并返回NIL
(format t "hello, world!")

;; 定义函数hello-world
(defun hello-world ()
  (format t "hello, world!"))

;; 调用函数hello-world
(hello-world)
;; 打印函数在屏幕中打印字符串
;; 并返回nil
(print "hello, world!")

;; 定义函数hello-world
(defn hello-world []
  (print "hello, world!"))

;; 调用函数hello-world
(hello-world)

阶乘编辑

Lisp语法自然的适合于递归。数学问题比如递归定义集合的枚举,在这种表示法中表达是很容易的。例如,要求值一个数的阶乘

(defun factorial (n)
    (if (zerop n) 1
        (* n (factorial (1- n)))))

下面的版本在底层Lisp系统进行尾递归优化时比前面版本取用更少的堆栈空间:

(defun factorial (n &optional (acc 1))
    (if (zerop n) acc
        (factorial (1- n) (* acc n))))

对比上述例子,下面的指令式版本使用了Common Lisploop宏:

(defun factorial (n)
    (loop for i from 1 to n
        for fac = 1 then (* fac i)
        finally (return fac)))

反转列表编辑

下列函数反转一个列表。它与Lisp内建的reverse函数做同样的事情:

(defun -reverse (list)
    (let ((return-value))
      (dolist (e list) (push e return-value))
      return-value))

Lisp 1.5的7个原始运算编辑

Paul GrahamJohn McCarthy的最初论文中提炼出如下7个原始运算[74]

quote编辑

(quote x)返回x,我们简记为'x

(quote a)

上面的列表達式的值是a。如果使用C语言或者Java語言的列表達方式,可以說成:上面這段代碼返回的值是a'a這個列表達式和上面的那個相同,值也是a。將quote寫成'只是一種語法糖

quote起來的單一個元素會成為符號(symbol,例如'a)。符號是Lisp中的一個特別概念,他在程式碼中看起來是個字串,但並不盡然,因為符號其實會被Lisp解释器直接指向某個記憶體位置,所以當你比較'apple'apple兩個符號是否相同時,不需要像字串一樣一個個字元逐字比較,而是直接比較記憶體位置,故速度較快(使用eq運算子來比較,如果使用equal運算子會變成逐字比較)。當你定義一個函数,或者定義一個变量時,他們的內容其實就是指向一個符號。

atom编辑

(atom x)x是一个atom或者空的list时返回原子t,否则返回NIL。在Common Lisp中我们习惯用原子t列表示真,而用空列表()NIL列表示假。

> (atom 'a)
t
> (atom '(a b c))
NIL
> (atom '())
t

现在我们有了第一个需要求出实际参数值的算符,让我们来看看quote算符的作用——通过引述一个列表,我们避免它被求值(eval)。一个未被引用的列表达式作为实际参数,atom将其视为代码,例如:

> (atom (atom 'a))
t

这是因为(atom 'a)的结果t被求出,并代入(atom (atom 'a)),成为(atom t),而这个列表达式的结果是t

反之一个被引用的列表仅仅被视为列表

> (atom '(atom 'a))
NIL

引用看上去有些奇怪,因为你很难在其它语言中找到类似的概念,但正是这一特征构成了Lisp最为与众不同的特点:代码和数据使用相同的结构来列表示,只用quote来区分它们。

eq编辑

(eq x y)xy指向相同的对象的时候返回t,否则返回NIL,值得注意的是在Common Lisp中,原子对象在内存中只会有一份拷贝,所以(eq 'a 'a)返回t,例如:

>(eq 'a 'a)
t
>(eq 'a 'b)
NIL
> (eq '() '())
t
> (eq '(a b c) '(a b c))
NIL

car编辑

(car x)要求x是一个列表,它返回x中的第一个元素,例如:

> (car '(a b))
a

cdr编辑

(cdr x)同样要求x是一个列表,它返回x中除第一个元素之外的所有元素组成的列表,例如:

> (cdr '(a b c))
(b c)

cons编辑

(cons x y)返回一个cons cell(x y),如果y不是一个list,将会以dotted pair形式展现这个cons cell,例如:

>(cons 'a 'b)
(a . b)

一个cons cell的第二项如果是另一个cons cell,就列表示成列表的形式,例如:

 (cons 'a (cons 'b 'c))

就列表示成 (a b . c) 若一个cons cell第二项为空,就省略不写,例如:

 (cons 'a  (cons 'b ()))

列表示为(a b)这样,多重的cons cell就构成了列表:

> (cons 'a (cons 'b (cons 'c ())))
(a b c)

cond编辑

(cond (p1 e1) ...(pn en))的求值规则如下:对“条件列表达式p”依次求值,直到有一个返回t。如果能找到这样的p列表达式,相应的“结果列表达式e”的值,作为整个cond列表达式的返回值。

> (cond ((eq 'a 'b) 'first)  ((atom 'a)  'second))
 second

Lisp的巨集编辑

Lisp的語法結構使数据与程序只是一線之隔(有quote就是数据,沒quote就是程序)。由于Lisp這種「数据即程序、程序即数据」的概念,使Lisp的巨集(Macro)變得非常有彈性:你可以定義巨集,指定它應該被編譯器翻譯(巨集展開)成什麼程式,程序和数据都可以靈活地互相轉換,最後展開的代码會成為整個程序的一部分。巨集的实现非常倚重quote來達成。當你定義了一個巨集,巨集被quote的部份會先被編譯器unquote,而沒有quote、或已經被unquote的部份,則會先被求值。最终編譯器生成的整個程序代码才是最後執行時的代码。以下以廣泛使用的Emacs Lisp為範例(以下範例亦相容Common Lisp),解釋最基本的Lisp巨集。

想要建立一個list並賦予給fruit這個变量時不能這樣做,因為這個list沒有被quote過,會被編譯器視為「程序」執行(會把"apple"這個字符串當成函数解釋),而不是「数据」,因而產生錯誤:

> (setq fruit ("apple" "banana" "citrus"))
錯誤"apple"不是一個有效函數

但這樣就正確了:

> (setq fruit (quote ("apple" "banana" "citrus")))
("apple" "banana" "citrus")
;;或者
> (setq fruit '("apple" "banana" "citrus"))
("apple" "banana" "citrus")
;;也可以用(list...)運算子,這樣一樣可以建立list。因為list本身是個函數,本來就應該被當成程式執行而不是資料,所以不會報錯:
> (setq fruit (list "apple" "banana" "citrus"))
("apple" "banana" "citrus")

前面有提到使用'符號這個語法糖能夠代替quote,但還有另外一種符號是`,意義基本上與'相同,但被`包起來的部份可以再用來unquote;而'沒有這種能力。

也就是說被`給quote起來的部份是資料,但使用逗号“,”來unquote,令被quote的数据變回程序。(注意quote只有一個arg,所以arg要用list包起來)

;;使用`來quote整個list
> `("apple" "banana" "citrus")
("apple" "banana" "citrus")

;;使用逗號,來unquote,這樣fruit這個變量會被重新求值。
> `("apple" "banana" "citrus" ,fruit)
("apple" "banana" "citrus" ("apple" "banana" "citrus"))

;;可以利用unquote的特性,定義一個函数,讓該函数根據輸入的參數回傳一個我們想要的list数据結構:
(defun user-profile (name email mobile)
  `((name . ,name)
    (email . ,email)
    (mobile . ,mobile)))

(user-profile "Richard" "rms@gnu.org" "Noooo!")
=> ((name . "Richard")
    (email . "rms@gnu.org")
    (mobile . "Noooo!"))

簡易巨集範例编辑

這裡定義一個巨集叫做nonsense,這個巨集可以方便地定義更多以nonsense為開頭的新函數:

(defmacro nonsense (function-name)
  `(defun ,(intern (concat "nonsense-" function-name)) (input) ;intern可以將string轉成symbol
     (print (concat ,function-name input))))
;;解釋:
;;這個巨集在編譯時,`(defun  因為被quote所以不會被求值,
;;但裡面的,(intern ...)這一段從逗號開始,整個括號括起來的
;; s-expression部份會被求值。這時作為argument輸入的function-name
;;就是在這時被插進macro中。其餘剩下的部份因為仍然在`(defun的quote
;;影響之下,並不會被求值。
;;現在巨集展開完了,整個巨集才被當成一般function執行。
 
(nonsense "apple")  ;使用我們剛剛定義的nonsense這個macro來定義新的f函数
=> nonsense-apple  ;成功定義出了新的函数叫做nonsense-apple

(nonsense "banana")  ;再使用一次巨集來定義新的函数叫做nonsense-banana
=> nonsense-banana  ;成功定義了新的函数。

(nonsense-apple " is good")  ;使用剛剛定義出的新函数
=> "apple is good"
(nonsense-banana " I love to eat")  ;使用另一個剛剛定義函数
=> "banana I love to eat"

对象系统编辑

已经有各种对象系统和模型建造在Lisp之上、旁边或其中,包括:

参见编辑

参考文献编辑

  1. ^ Introduction. The Julia Manual. Read the Docs. [2016-12-10]. (原始内容存档于2016-04-08). 
  2. ^ Wolfram Language Q&A. Wolfram Research. [2016-12-10]. 
  3. ^ Edwin D. Reilly. Milestones in computer science and information technology. Greenwood Publishing Group. 2003: 156–157. ISBN 978-1-57356-521-9. 
  4. ^ McCarthy, John. History of Lisp (PDF). Artificial Intelligence Laboratory, Stanford University. 12 February 1979. There were two motivations for developing a language for the IBM 704. First, IBM was generously establishing a New England Computation Center at M.I.T. …… Second, IBM was undertaking to develop a program for proving theorems in plane geometry (based on an idea of Marvin Minsky’s), ……. ……
    In connection with IBM’s plane geometry project, Nathaniel Rochester and Herbert Gelernter英语Herbert Gelernter (on the advice of McCarthy) decided to implement a list processing language within FORTRAN, ……. This work was undertaken by Herbert Gelernter and Carl Gerberich at IBM and led to FLPL, standing for FORTRAN List Processing Language. ……
    I spent the summer of 1958 at the IBM Information Research Department at the invitation of Nathaniel Rochester and chose differentiating algebraic expressions as a sample problem. It led to the following innovations beyond FLPL:
    a. Writing recursive function definitions using conditional expressions. ……
    b. The maplist function that forms a list of applications of a functional argument to the elements of a list. ……
    c. To use functions as arguments, one needs a notation for functions, and it seemed natural to use the λ-notation of Church (1941). I didn’t understand the rest of his book, so I wasn’t tempted to try to implement his more general mechanism for defining functions. Church used higher order functionals instead of using conditional expressions. Conditional expressions are much more readily implemented on computers.
    d. The recursive definition of differentiation made no provision for erasure of abandoned list structure. ……
    The implementation of LISP began in Fall 1958. …… The programs to be hand-compiled were written in an informal notation called M-expressions英语M-expression intended to resemble FORTRAN as much as possible.
     
  5. ^ SICP: Foreword. (原始内容存档于2001-07-27). Lisp is a survivor, having been in use for about a quarter of a century. Among the active programming languages only Fortran has had a longer life. 
  6. ^ Conclusions. [2014-06-04]. (原始内容存档于2014-04-03). 
  7. ^ David Turner英语David Turner (computer scientist). Some History of Functional Programming Languages (PDF). LISP was not based on the lambda calculus, despite using the word “LAMBDA” to denote functions. At the time he invented LISP, McCarthy was aware of (Church 1941) but had not studied it. The theoretical model behind LISP was Kleene’s theory of first order recursive functions. (McCarthy made these statements, or very similar ones, in a contribution from the floor at the 1982 ACM symposium on LISP and functional programming in Pittsburgh. No written version of this exists, as far as know.) 
    Michael J. Fischer英语Michael J. Fischer. Lambda-Calculus Schemata (PDF). LISP AND SYMBOLIC COMPUTATION: An International Journal, 6, 259–288. 1993. Pure LISP allows the definition and evaluation of functions over S-expressions. The lambda notation for functional abstraction is borrowed from Church’s lambda calculus, but otherwise there is little similarity between the two systems. Pure LISP has no higher-order functions, and call-by-value evaluation order is implicitly assumed. Two special constructs, conditional expressions and the label operator, allow recursive functions to be defined. Limited as it is, pure LISP is nevertheless powerful enough to express all partial recursive functions and hence provides an adequate basis for a theory of computation. 
  8. ^ The Art of the Interpreter, or the Modularity Complex (Parts Zero, One, and Two), Part Zero, P. 4. MIT Libraries. [2020-08-01]. hdl:1721.1/6094. 
  9. ^ The Top Programming Languages in Artificial Intelligence. Artificial Intelligence. APRO. [2021-02-15]. (原始内容存档于2020-10-30). 
  10. ^ John McCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, Michael I. Levin. LISP 1.5 Programmer's Manual (PDF) 2nd. MIT Press. 1985 [1962]. ISBN 0-262-13011-4. A function can be simply a name. In this case its meaning must be previously understood. A function may be defined by using the lambda notation and establishing a correspondence between the arguments and the variables used in a form. If the function is recursive, it must be given a name by using a label. ……
    When a symbol stands for a function, the situation is similar to that in which a symbol stands for an argument. When a function is recursive, it must be given a name. This is done by means of the form LABEL, which pairs the name with the function definition on the a-list. The name is then bound to the function definition, just as a variable is bound to its value.
    In actual practice, LABEL is seldom used. It is usually more convenient to attach the name to the definition in a uniform manner. This is done by putting on the property list of the name, the symbol EXPR followed by the function definition. The pseudo-function define used at the beginning of this section accomplishes this. When apply interprets a function represented by an atomic symbol, it searches the p-list of the atomic symbol before searching the current a-list. Thus a define will override a LABEL.
     
  11. ^ Paul Graham. Revenge of the Nerds. [2013-03-14]. 
  12. ^ Chisnall, David. Influential Programming Languages, Part 4: Lisp. 2011-01-12. 
  13. ^ Jones, Robin; Maynard, Clive; Stewart, Ian. The Art of Lisp Programming. Springer Science & Business Media. December 6, 2012: 2. ISBN 9781447117193. 
  14. ^ John McCarthy. Recursive functions of symbolic expressions and their computation by machine, Part I. (PDF). Communications of the ACM (ACM New York, NY, USA). 1960, 3 (4): 184–195. doi:10.1145/367177.367199. 
  15. ^ David Canfield Smith. MLISP Users Manual (PDF). [2006-10-13]. 
  16. ^ 16.0 16.1 John McCarthy; Robert Brayton; Daniel Edwards; Phyllis Fox英语Phyllis Fox; Louis Hodes英语Louis Hodes; David Luckham英语David Luckham; Klim Maling; David Park英语David Park (computer scientist); Steve Russell. LISP I Programmers Manual (PDF). Boston, Massachusetts: Artificial Intelligence Group, M.I.T. Computation Center英语M.I.T. Computation Center and Research Laboratory. March 1960. 
  17. ^ Tim Hart and Mike Levin. AI Memo 39-The new compiler (PDF). [2019-03-18]. (原始内容 (PDF)存档于2020-12-13). 
  18. ^ Hart, Timothy P. AIM-057, MACRO Definitions for LISP, Timothy P. Hart. October 1963. hdl:1721.1/6111. 
  19. ^ Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978. SCHEME is a dialect of LISP. It is an expression-oriented, applicative order, interpreter-based language which allows one to manipulate programs as data.
    It differs from most current dialects of LISP in that it closes all lambda-expressions in the environment of their definition or declaration, rather than in the execution environment. This has the consequence that variables are normally lexically scoped, as in ALGOL. However, in contrast with ALGOL, SCHEME treats procedures as a first-class data type. They can be the values of variables, the returned values of procedures, and components of data structures.
    Another difference from LISP is that SCHEME is implemented in such a way that tail-recursions execute without net growth of the interpreter stack. The effect of this is that a procedure call behaves like a GOTO and thus procedure calls can be used to implement iterations, as in PLASMA.
     
  20. ^ The 36-bit word size of the PDP-6/PDP-10 was influenced by the usefulness of having two Lisp 18-bit pointers in a single word. Peter J. Hurley. The History of TOPS or Life in the Fast ACs. Newsgroupalt.folklore.computers. 18 October 1990. Usenet: 84950@tut.cis.ohio-state.edu. The PDP-6 project started in early 1963, as a 24-bit machine. It grew to 36 bits for LISP, a design goal. 
  21. ^ Gerald Jay Sussman, Terry Winograd. Micro-planner Reference Manual. AI Memo No, 203, MIT Project MAC. 1970. 
  22. ^ 22.0 22.1 John McCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, Michael I. Levin. LISP 1.5 Programmer's Manual (PDF) 2nd. MIT Press. 1985 [1962]. ISBN 0-262-13011-4. To define these functions, we use the pseudo-function define. …… A pseudo-function is a function that is executed for its effect on the system in core memory, as well as for its value. define causes these functions to be defined and available within the system. Its value is a list of the functions defined ……. 
    Kent M. Pitman英语Kent Pitman. The Revised Maclisp Manual. 1983, 2007. DEFUN Special Form (DEFUN namespec . definitionspec)
    DEFUN offers a way of associating a functional definition with a symbol. …… DEFUN can be used to define both normal functions and special forms (fexprs and macros). ……
    ;; Normal expr or lexpr function definitions
    (DEFUN name bvl . body)

    ……
    If name is a symbol and bvl is a list of symbols which is free of &keywords (to be described below), the definition is an expr or lexpr definition. In Maclisp, this would be essentially equivalent to writing
    (DEFPROP name (LAMBDA bvl . body) EXPR).
    …… Note also that the keyword EXPR is used for both expr and lexpr definitions. The only distinction between expr and lexpr definitions is whether the bvl is a symbol or a list, but they are specified in the same way and looked up from the same property. ……
    A fexpr英语fexpr is a function for which the formal parameters are not evaluated. The form of a fexpr definition is:
    (DEFUN name FEXPR (sym) . body).
    The lambda expression which describes a fexpr should expect to receive exactly one argument which will be the list of (unevaluated) arguments given in the call to the fexpr, so usually there is only one bound variable (sym) in the bound variable list. ……
    DEFUN can also be used to instantiate a MACRO definition. …… The syntax for a macro definition is
    (DEFUN name MACRO (sym) . body),
    where sym will become bound to the whole macro form to be expanded (including the name). Note that this argument convention is different than fexprs, which only receive the cdr of the call form. ……
    DEFUN was introduced into Maclisp in March, 1969. Although now it is recognized as the standard function defining form because it shields the user from the implementational details of how the function is defined ……. ……
    DEFPROP Function (DEFPROP sym val indicator)
    Gives sym a property called indicator with value val. The arguments are not evaluated. DEFPROP should not be used imbedded in other expressions. It is intended to occur at toplevel to assign properties that are set up once and never changed. In other places, use PUTPROP with three quoted arguments.
     
  23. ^ Gerald J. Sussman, Guy L. Steele Jr..   Scheme: An Interpreter for Extended Lambda Calculus. 1975. "DEFINE - This is analogous to the MacLISP DEFUN primitive (but note that the LAMBDA must appear explicitly!). It is used for defining a function in the "global environment" permanently, as opposed to LABELS (see below), which is used for temporary definitions in a local environment. DEFINE takes a name and a lambda expression; it closes the lambda expression in the global environment and stores the closure in the LISP value cell of the name (which is a LISP atom)." 
    Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978. Atoms which are not atomic symbols (identifiers) evaluate to themselves. Typical examples of such atoms are numbers, arrays, and strings (character arrays). Symbols are treated as identifiers or variables. They may be lexically bound by lambda-expressions. There is a global environment containing values initially have as their values primitive operations such as, for example, CAR, CONS, and PLUS. SCHEME differs from most LISP systems in that the atom CAR is not itself an operation (in the sense of being an invocable object, e.g. a valid first argument to APPLY), but only has one as a value when considered as an identifier. 
  24. ^ Richard P. Gabriel英语Richard P. Gabriel; Kent M. Pitman英语Kent M. Pitman. Technical Issues of Separation in Function Cells and Value Cells. Lisp and Symbolic Computation. June 1988, 1 (1): 81–101. S2CID 26716515. doi:10.1007/bf01806178. In this paper, we shall refer to two abstract dialects of Lisp called Lisp1 and Lisp2.
    Lisp1 has a single namespace that serves a dual role as the function namespace and value namespace; that is, its function namespace and value namespace are not distinct. In Lisp1, the functional position of a form and the argument positions of forms are evaluated according to the same rules. Scheme [Rees 1986] and the language being designed by the EuLisp group [Padget 1986] are Lisp1 dialects.
    Lisp2 has distinct function and value namespaces. In Lisp2, the rules for evaluation in the functional position of a form are distinct from those for evaluation in the argument positions of the form. Common Lisp is a Lisp2 dialect. ……
    Most Lisp dialects adopted a two-namespace approach to the naming problem. To some extent this is because most dialects followed Lisp 1.5 [McCarthy 1965] or dialects derived from Lisp 1.5.
    Lisp 1.5 broke symbols into values and functions; values were stored on an association list英语association list, and function on the property lists of symbols. Compiled and interpreted code worked in different ways. ……
    MacLisp [Pitman 1983] is a direct descendant of the PDP-6 Lisp and is a Lisp2 dialect. MacLisp uses a sophisticated form of link table, which is made possible by the separation of namespaces. In particular, function-defining functions have controlled access into the places where functions are stored so that the link tables can be correctly maintained. ……
    Common Lisp was the result of a compromise between a number of dialects of Lisp, most of them descendants of MacLisp, all of them Lisp2s.
     
  25. ^ John McCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, Michael I. Levin. LISP 1.5 Programmer's Manual (PDF) 2nd. MIT Press. 1985 [1962]. ISBN 0-262-13011-4. 
  26. ^ Quam, Lynn H.; Diffle, Whitfield. Stanford LISP 1.6 Manual (PDF). 
  27. ^ Maclisp Reference Manual. March 3, 1979. 
  28. ^ Teitelman, Warren. InterLisp Reference Manual (PDF). 1974. 
  29. ^ Package: lang/lisp/impl/xlisp/. cs.cmu.edu. 
  30. ^ Outils de generation d’interfaces : etat de l’art et classification by H. El Mrabet
  31. ^ Gerald J. Sussman, Guy L. Steele Jr. SCHEME: An Interpreter for Extended Lambda Calculus. 1975. Inspired by ACTORS [Greif and Hewitt][Smith and Hewitt], we have implemented an interpreter for a LISP-like language, SCHEME, based on the lambda calculus [Church], but extended for side effects, multiprocessing, and process synchronization. The purpose of this implementation is tutorial. We wish to:
    ⑴ alleviate the confusion caused by Micro-PLANNER英语Planner (programming language), CONNIVER, etc. by clarifying the embedding of non-recursive control structures in a recursive host language like LISP.
    ⑵ explain how to use these control structures, independent of such issues as pattern matching and data base manipulation.
    ⑶ have a simple concrete experimental domain for certain issues of programming semantics and style.
     
  32. ^ Steele, Guy L., Jr. Purpose. Common Lisp the Language 2nd. 1990. ISBN 0-13-152414-3. 
  33. ^ Kantrowitz, Mark; Margolin, Barry. History: Where did Lisp come from?. FAQ: Lisp Frequently Asked Questions 2/7. 20 February 1996. 
  34. ^ ISO/IEC 13816:1997. Iso.org. 2007-10-01 [2013-11-15]. 
  35. ^ ISO/IEC 13816:2007. Iso.org. 2013-10-30 [2013-11-15]. 
  36. ^ X3J13 Charter. 
  37. ^ Guy L. Steele: Common Lisp the Language, 1st Edition, Digital Press, 1984, ISBN 0-932376-41-X
  38. ^ The Road To Lisp Survey. [2006-10-13]. (原始内容存档于2006-10-04). 
  39. ^ Trends for the Future. Faqs.org. [2013-11-15]. (原始内容存档于2013-06-03). 
  40. ^ Weinreb, Daniel. Common Lisp Implementations: A Survey. [4 April 2012]. (原始内容存档于2012-04-21). 
  41. ^ LISP50@OOPSLA. Lisp50.org. [2013-11-15]. 
  42. ^ The Revised5 Report on the Algorithmic Language Scheme. schemers.org. 1998. 
  43. ^ The Revised6 Report on the Algorithmic Language Scheme. 2007. 
  44. ^ Why MIT now uses python instead of scheme for its undergraduate CS program. cemerick.com. March 24, 2009 [November 10, 2013]. (原始内容存档于September 17, 2010). 
  45. ^ Broder, Evan. The End of an Era. mitadmissions.org. January 8, 2008 [November 10, 2013]. 
  46. ^ Clemens Fruhwirth. Liskell - Haskell Semantics with Lisp Syntax (PDF). 2007. 
  47. ^ a specification for Bel, "a new dialect of Lisp."
  48. ^ Chapter 1.1.2, History, ANSI CL Standard
  49. ^ [1] Clasp is a Common Lisp implementation that interoperates with C++ and uses LLVM for just-in-time compilation (JIT) to native code.
  50. ^ [2] "Armed Bear Common Lisp (ABCL) is a full implementation of the Common Lisp language featuring both an interpreter and a compiler, running in the JVM"
  51. ^ [3] 互联网档案馆存檔,存档日期2018-06-22. Common Lisp Implementations: A Survey
  52. ^ [4] Comparison of actively developed Common Lisp implementations
  53. ^ Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998. Sussman had just been studying Algol. He suggested starting with a lexically scoped dialect of Lisp, because that seemed necessary to model the way names could refer to acquaintances in PLASMA. Lexical scoping would allow actors and functions to be created by almost identical mechanisms. Evaluating a form beginning with the word lambda would capture the current variable-lookup environment and create a closure; evaluating a form beginning with the word alpha would also capture the current environment but create an actor. Message passing could be expressed syntactically in the same way as function invocation. The difference between an actor and a function would be detected in the part of the interpreter traditionally known as apply. A function would return a value, but an actor would never return; instead, it would typically invoke a continuation, another actor that it knew about. Our interpreter also provided the necessary primitives for implementing the internal behavior of primitive actors, such as an addition operator that could accept two numbers and a continuation actor. ……
    …… This led us to three important ideas:
    • First, we realized that all the patterns of control structure that Hewitt had described in terms of actors could equally well be described by the λ-calculus. ……
    • Second, we realized that the λ-calculus — a small, simple formalism — could serve as the core of a powerful and expressive programming language. (Lisp had adopted the λ-notation for functions but had failed to support the appropriate behavior for free variables. The original theoretical core of Lisp was recursion equations, not the λ-calculus.) ……
    • Third, we realized that in our quest for the “ultimate AI language” we had come full circle. As the MIT school had struggled to find more and more powerful ways to express and manipulate control structure to support heuristic search, we had progressed from Lisp to CONVERT to Planner to Conniver to PLASMA to a simple variation of Lisp!
     
  54. ^ An In-Depth Look at Clojure Collections, Retrieved 2012-06-24
  55. ^ Clojure rational. [27 August 2019]. Clojure is a Lisp not constrained by backwards compatibility 
  56. ^ Script-fu In GIMP 2.4, Retrieved 2009-10-29
  57. ^ librep at Sawfish Wikia, retrieved 2009-10-29
  58. ^ IEEE Scheme. IEEE 1178-1990 - IEEE Standard for the Scheme Programming Language. [27 August 2019]. 
  59. ^ The Jargon File - Lisp. [2006-10-13]. 
  60. ^ John McCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, Michael I. Levin. LISP 1.5 Programmer's Manual (PDF) 2nd. MIT Press. 1985 [1962]. ISBN 0-262-13011-4. Special Forms - Normally, eval evaluates the arguments of a function before applying the function itself. Thus if eval is given (CONS X Y), it will evaluate X and Y, and then cons them. But if eval is given (QUOTE X), X should not be evaluated. QUOTE is a special form that prevents its argument from being evaluated.
    A special form differs from a function in two ways. Its arguments are not evaluated before the special form sees them. COND, for example, has a very special way of evaluating its arguments by using evcon. The second way which special forms differ from functions is that they may have an indefinite number of arguments. Special forms have indicators on their property lists called FEXPR and FSUBR for LISP-defined forms and machine language coded forms, respectively.
     
  61. ^ John McCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, Michael I. Levin. LISP 1.5 Programmer's Manual (PDF) 2nd. MIT Press. 1985 [1962]. ISBN 0-262-13011-4. The variables in a lambda expression are dummy or bound variables because systematically changing them does not alter the meaning of the expression. Thus λ[[u;v];v2+u] means the same thing as λ[[x;y];y2+x].
    We shall sometimes use expressions in which a variable is not bound by a lambda. For example, in the function of two variables λ[[x;y];xn+yn] the variable n is not bound. This is called a free variable. It may be regarded as a parameter. Unless n has been given a value before trying to compute with this function, the value of the function must be undefined. ……
    Free Variables - A variable is bound in a particular function when it occurs in a list of bound variables following the word LAMBDA or PROG. Any variable that is not bound is free. ……
    When a variable is used free, it must have been bound by a higher level function. If a program is being run interpretively, and a free variable is used without having bee bound on a higher level, error diagnostic *A 8* will occur.
    If the program is being run complied, the diagnostic may not occur, and the variable may have value NIL.
     
  62. ^ Gerald J. Sussman, Guy L. Steele Jr..   Scheme: An Interpreter for Extended Lambda Calculus. 1975. "There are several important consequences of closing every lambda expression in the environment from which it is passed (i.e., in its "lexical" or "static" environment).
    First, the axioms of lambda calculus are automatically preserved. Thus, referential transparency is enforced. This in turn implies that there are no "fluid" variable bindings (as there are in standard stack implementations of LISP such as MacLISP).
    Second, the upward funarg problem英语funarg problem [Moses] requires that the environment structure be potentially tree-like.
    Finally, the environment at any point in a computation can never be deeper than the lexical depth of the expression being evaluated at that time; i.e., the environment contains bindings only for variables bound in lambdas lexically surrounding the expression being evaluated. This is true even if recursive functions are involved. ……Furthermore, it is not even necessary to scan the environment for the variable, since its value must be in a known position relative to the top of the environment structure; this position can be computed by a compiler at compile time on the basis of lexical scope."
     
  63. ^ Alexander Burger. PicoLisp Frequently Asked Questions. Why do you use dynamic variable binding? Dynamic binding is very powerful, because there is only one single, dynamically changing environment active all the time. This makes it possible (e.g. for program snippets, interspersed with application data and/or passed over the network) to access the whole application context, freely, yet in a dynamically controlled manner. And (shallow) dynamic binding is the fastest method for a Lisp interpreter.
    Lexical binding is more limited by definition, because each environment is deliberately restricted to the visible (textual) static scope within its establishing form. Therefore, most Lisps with lexical binding introduce "special variables" to support dynamic binding as well, and constructs like labels to extend the scope of variables beyond a single function.
    In PicoLisp, function definitions are normal symbol values. They can be dynamically rebound like other variables. ……
    Are there no problems caused by dynamic binding? You mean the funarg problem, or problems that arise when a variable might be bound to itself? For that reason we have a convention in PicoLisp to use transient symbols (instead of internal symbols) or private internal symbols ……
    But with dynamic binding I cannot implement closures! This is not true. Closures are a matter of scope, not of binding.
    For a closure it is necessary to build and maintain a separate environment. In a system with lexical bindings, this has to be done at each function call, and for compiled code it is the most efficient strategy anyway, because it is done once by the compiler, and can then be accessed as stack frames at runtime.
    For an interpreter, however, this is quite an overhead. So it should not be done automatically at each and every function invocation, but only if needed.
     
  64. ^ Lutz Mueller. Comparison to Common Lisp and Scheme. Dynamic scoping inside isolated namespaces - newLISP is sometimes criticized for using dynamic scoping and fexprs. …… In newLISP, all variables are dynamically scoped by default. However, by defining a function in its own context, static/lexical scoping can be achieved. In newLISP, several functions and data can share a namespace. By enclosing functions in their own namespace, a lexical closure- like mechanism is achieved. Common Lisp and Scheme are lexically scoped by default and use lambda expressions as the closure mechanism. Common Lisp also offers special variables for dynamic scoping.
    The problems of free variables in dynamic scoping can be avoided. In the rare cases when free variables must be used, you can partition code into namespace modules for easier control over free variables. You can then exploit the advantages of dynamic scoping. With dynamic scoping inside lexically-closed namespaces, newLISP combines the best of both scoping worlds.
    newLISP has no funarg problem because it follows a simple rule: variables always show the binding of their current environment. When expressions with local variables are entered, newLISP saves the current variable state on a stack and restores it on exit of that expression. In newLISP, not only are function parameters and variables declared with let expressions local, loop variables in all looping expressions are local too.
     
  65. ^ Dynamic Binding Vs Lexical Binding. EmacsWiki. dynamic - All variable names and their values live in one global table. lexical - Each binding scope (function, let syntax, …) creates a new table of variable names and values, organised in a hierarchy called “the environment”. ……
    EmacsLisp as of 24.1 has both dynamic binding and lexical binding. Lexical binding must be enabled explicitly for a file or buffer. Individual variables can be 'defvar'ed to make them “special”, like in CommonLisp.
     
  66. ^ Sebesta, Robert W. "2.4 Functional Programming: LISP";"6.9 List Types";"15.4 The First Functional Programming Language: LISP". Concepts of Programming Languages (print) 10th. Boston, MA, USA: Addison-Wesley. 2012: 47–52;281–284;677–680. ISBN 978-0-13-139531-2 (英语). 
  67. ^ NB: a so-called "dotted list" is only one kind of "improper list". The other kind is the "circular list" where the cons cells form a loop. Typically this is represented using #n=(...) to represent the target cons cell that will have multiple references, and #n# is used to refer to this cons. For instance, (#1=(a b) . #1#) would normally be printed as ((a b) a b) (without circular structure printing enabled), but makes the reuse of the cons cell clear. #1=(a . #1#) cannot normally be printed as it is circular, although (a...) is sometimes displayed, the CDR of the cons cell defined by #1= is itself.
  68. ^ CSE 341: Scheme: Quote, Quasiquote, and Metaprogramming. Cs.washington.edu. 1999-02-22 [2013-11-15]. 
  69. ^ Alan Bawden. Quasiquotation in Lisp. 1999. 
  70. ^ 3.2.2.3 Semantic Constraints in Common Lisp HyperSpec
  71. ^ 4.3. Control Abstraction (Recursion vs. Iteration) in Tutorial on Good Lisp Programming Style by Kent Pitman英语Kent Pitman and Peter Norvig, August, 1993.
  72. ^ Time of Evaluation - Common Lisp Extensions. Gnu.org. Retrieved on 2013-07-17.
  73. ^ Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978. Magic forms are recognized by then presence of a "magic (reserved) word" in the car position of the form. All non-atomic forms which are not magic forms are considered to be combinations. The system has a small initial set of magic words; there is also a mechanism for creating new ones {Note FUNCALL is a Pain}.
    A combination is considered to be a list of subforms. These subforms are all evaluated. The first value mub be a procedure; it is applied to the other values to get the value of the combination. There are four important points here:
    (1) the procedure Position is always evaluated just like any other position. (This is why the primitive operators are the values of global identifiers.)
    (2) the procedure is never "re-evaluated"; if the first subform fails to evaluate to a applicable procedure, it is an error. Thus, unlike most LISP systems, SCHEME always evaluates the first subform of a combination exactly once.
    (3) the arguments are all completely evaluated before the procedure is applied; that is, SCHEME, like most LISP systems, is an applicative-order language. Many SCHEME programs exploit this fact.
    (4) the argument forms (and procedure form) may in principle be evaluated in any order. This is unlike the usual LISP left-to-right order. (All SCHEME interpreters implemented so far have in fact performed left-to-right evaluation, but we do not wish programs to depend on this fact. Indeed, there are some reasons why a clever interpreter might want to evaluate them right-to-left, e.g. to get things on a stack in the correct order.)
     
  74. ^ Paul Graham. The Roots of Lisp (PDF). 2002. 
  75. ^ Daniel G. Bobrow, Kenneth Kahn, Gregor Kiczales, Larry Masinter, Mark Stefik, Frank Zdybel. CommonLoops: Merging Lisp and Object-Oriented Programming. 1986. 
  76. ^ "A History and Description of CLOS", by Jim Veitch. Pages 107–158 of Handbook of Programming Languages, Volume IV: Functional and Logic Programming Languages, ed. Peter H. Salus英语Peter H. Salus. 1998 (1st edition), Macmillan Technical Publishing; ISBN 1-57870-011-6

延伸阅读编辑

外部链接编辑

历史
图书和教程
资源