本页使用了标题或全文手工转换

LISP

维基百科,自由的百科全书
跳到导航 跳到搜索
Lisp
Lisplogo.png
编程范型 多范型函数式过程式反射式元编程
设计者 约翰·麦卡锡
实现者 史帝芬·罗素, Timothy P. Hart和Mike Levin
发行时间 1958年,​61年前​(1958
类型系统 动态类型强类型
派生副语言
Arc, AutoLISP, Clojure, Common Lisp, Emacs Lisp, ISLISP, Logo, newLISP英语newLISP, Racket, Scheme, SKILL英语Cadence SKILL
启发语言
IPL
影响语言
CLU, Dylan英语Dylan, Falcon, Forth, Haskell, Io语言, Ioke英语Ioke, JavaScript, Lua, LPC, MDL,ML, Nu语言英语Nu_(programming_language), OPS5英语OPS5, Perl, Python, REBOL, Ruby, Smalltalk, Wolfram语言

Lisp(历史上拼写为LISP)是具有悠久历史的电脑编程语言家族,有独特和完全括号的前缀符号表示法。起源于公元1958年,是现今第二悠久而仍广泛使用的高端编程语言。只有FORTRAN编程语言比它更早一年。Lisp编程语族已经演变出许多种方言。现代最著名的通用编程语种是ClojureCommon LispScheme

Lisp最初创建时受到阿隆佐·邱奇lambda演算的影响,用来作为电脑程序实用的数学表达。因为是早期的高端编程语言之一,它很快成为人工智慧研究中最受欢迎的编程语言。在电脑科学领域,Lisp开创了许多先驱概念,包括:树结构自动存储器管理动态类型条件表达式高端函数递归、自主(self-hosting)编译器、读取﹣求值﹣输出循环英语:Read-Eval-Print Loop,REPL)。

"LISP"名称源自“列表处理器”(英语:LISt Processor)的缩写。列表是Lisp的主要数据结构之一,Lisp编程代码也同样由列表组成。因此,Lisp程序可以把原始码当作数据结构进行操作,而使用其中的宏系统,开发人员可将自己定义的新语法或领域专用的语言,嵌入在Lisp编程中。

代码和数据的可互换性为Lisp提供了立即可识别的语法。所有的Lisp程序代码都写为S-表达式或以括号表示的列表。函数调用或语义形式也同样写成列表,首先是函数或操作符的名称,然后接着是一或多个参数:例如,取三个参数的函数f即为(f arg1 arg2 arg3)

Lisp语言的主要现代版本包括Common Lisp, SchemeRacket以及Clojure。1980年代盖伊·史提尔二世编写了Common Lisp试图进行标准化,这个标准被大多数解释器和编译器所接受。还有一种是编辑器Emacs所派生出来的Emacs Lisp(而Emacs正是用Lisp作为扩展语言进行功能扩展)非常流行,并创建了自己的标准。

历史[编辑]

20世纪[编辑]

1955年至1956年间,信息处理语言被创造出来,用于人工智慧处理(早期的基于符号处理的人工智慧领域,以图灵测试为目标)。此领域中有研究者持有观点:“符号演算系统可以派生出智慧。”[1]。它首先使用了列表(抽象数据类型)与递归。

1958年,约翰·麦卡锡麻省理工学院发明了Lisp编程语言,采用了信息处理语言的特征。1960年,他在《ACM通讯》发表论文,名为《递归函数的符号表达式以及由机器运算的方式,第一部》(Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I)。在这篇论文中阐述了只要透过一些简单的运算符,以及用于函数的记号,就可以创建一个具图灵完全性语言,可用于算法中。

麦卡锡最初使用M-表达式写代码,之后再转成S-表达式,举例来说M-表达式的语法,car[cons[A,B]],等同于S-表达式的(car (cons A B))。然而由于S-表达式具备同像性的特性(homoiconic,即程序与数据由同样的结构存储),实际应用中一般只使用S-表达式。此外他也借用了信息处理语言中的许多概念。

约翰·麦卡锡的学生史帝芬·罗素在阅读完此论文后,认为Lisp编程语言当中的eval函数可以用机器代码来实做。他在IBM 704机器上,写出了第一个Lisp解释器。1962年,蒂姆·哈特(Tim Hart)与麦克·莱文(Mike Levin)在麻省理工学院以Lisp编程语言,实做出第一个完整的Lisp编译器。这两人在笔记中使用的语法比麦卡锡早期的代码更接近现代Lisp风格。

然而使用20世纪70年代当时的编译器技术和硬件,要实现Lisp还是困难的挑战。由研究生丹尼尔·爱德华兹所开发的垃圾收集程序,使得在通用电脑上运行Lisp变得实用,但效率仍然是一个问题。这导致了Lisp专用机器的创建:用于运行Lisp环境和程序的专用硬件。之后电脑硬件和编译器技术的发展迅速,使得昂贵的Lisp专用机器过时。

公元2000年迄今[编辑]

在20世纪90年代衰退之后,Lisp最近十年来因一些关注而逐渐复苏。大多数新活动都集中在Common Lisp,Clojure,Racket,Scheme和Emacs Lisp的实现上,包括开发新的跨平台库和应用。当其他人认为Lisp已经是过时陈旧的,如保罗·格雷厄姆埃里克·雷蒙等人继续出版有关于Lisp编程的著作,一些新的开发人员受到这些作者启发,经常将Lisp这种语言描述为令人大开眼界的经验,并声称在本质上比较其它编程语言更有生产效率。这种意识的提高可对比于,如同Lisp在90年代中期“人工智慧冬季”的短暂增长。

Dan Weinreb在他的调查中,列出了11个积极维护中的Common Lisp实现。Scieneer Common Lisp是一个新的实现商业化产品,由CMUCL于2002年首次发布。

开源社群创建了新的支持基础:CLiki是个收集Common Lisp相关信息的维基,Common Lisp目录列出了资源,#lisp是一个受欢迎的IRC频道,可以共享和注释代码片段(在lisppaste的支持下,一个用Lisp编写的IRC机器人),Planet Lisp收集了各种 Lisp相关博客的内容,LispForum用户讨论Lisp主题,Lispjobs是个公布职缺机会的服务,还有一个Weekly Lisp News提供每周新闻。Common-lisp.net是开源项目的托管站点。Quicklisp则是含括了许多库的装载管理器。

Lisp50@OOPSLA庆祝了Lisp的50周年(1958-2008)。在波士顿,温哥华和汉堡有定期的当地用户会议。其他活动包括欧洲共同Lisp会议,欧洲Lisp专题讨论会和国际Lisp研讨会。Scheme社群积极维护了二十多个实现。在过去几年中已开发了数个有意义的新实现(Chicken,Gambit,Gauche,Ikarus,Larceny,Ypsilon),Scheme社群广泛接纳了R5RS语言标准。Scheme需求实现过程创建了很多预备标准库和Scheme扩展功能。各种 Scheme实现的用户社群持续地增长。

一个新的语言标准化过程于2003年开始,并在2007年产生了R6RS标准,而使用Scheme介绍电脑科学课程的学校似乎有所减少。麻省理工学院的电脑科学入门课程,已经不再使用Scheme。

有几种新的Lisp方言:Arc,Hy,Nu,Clojure,Liskell,LFE(Lisp Flavored Erlang)和Racket。

Lisp编程语族时间轴[编辑]

基本介绍[编辑]

Lisp是第一个函数式程序语言,区别于C语言Fortran等命令型程序语言和JavaC#Objective-C等面向对象程序语言。由于历史的原因,Lisp长期以来被认为主要用于人工智慧领域,但Lisp并不是只为人工智慧而设计,而是一种通用的程序语言。

Lisp的表达式是一个原子(atom)或列表(list),原子又包含符号(symbol)与数值(number);列表是由零个或多个表达式组成的序列,表达式之间用空格分隔开,放入一对括号中,如:

abc
()
(abc xyz)
(a b (c) d)

最后一个列表是由四个元素构成的,其中第三个元素本身也是一个列表,这种又称为嵌套列表(nested list)。

正如算数表达式1+1有值2一样,Lisp中的表达式也有值,如果表达式e得出值v,我们说e返回v。如果一个表达式是一个表,那么我们把表中的第一个元素叫做操作符,其余的元素叫做自变量。

Lisp的7个公理(基本操作符)[编辑]

基本操作符1 quote[编辑]

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

(quote a)

上面的表达式的值是a。如果使用C语言或者Java语言的表达方式,可以说成:上面这段代码返回的值是a。

'a

这个表达式和上面的那个相同,值也是a。将quote写成 ' 只是一种语法糖

quote起来的单一个元素会成为符号(symbol,例如'a)。符号是Lisp中的一个特别概念,他在代码中看起来是个字符串,但并不尽然,因为符号其实会被Lisp解释器直接指向某个存储器位置,所以当你比较'apple和'apple两个符号是否相同时,不需要像字符串一样一个个字符逐字比较,而是直接比较存储器位置,故速度较快(使用eq运算符来比较,如果使用equal运算符会变成逐字比较)。当你定义一个函数,或者定义一个变量时,他们的内容其实就是指向一个符号。

基本操作符2 atom[编辑]

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

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

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

> (atom (atom 'a))
t

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

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

> (atom '(atom 'a))
NIL

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

基本操作符3 eq[编辑]

(eq x y)当x和y指向相同的对象的时候返回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

基本操作符4 car[编辑]

Contents of the Address part of Register number缩写

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

> (car '(a b))
a

基本操作符5 cdr[编辑]

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

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

基本操作符6 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)

基本操作符7 cond[编辑]

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

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

函数[编辑]

七个原始操作符中,除了quote与cond,其他五个原始操作符总会对其自变量求值。我们称这样的操作符为函数。

语法和语义[编辑]

Lisp编程语族基本Hello World示例[编辑]

Scheme[编辑]

(display "Hello, world!")
;; 在屏幕中打印出:Hello, world!

;; 函数定义
(define (hello)
  (display "Hello, world!"))
 
;; 函数调用
(hello)
;; 在屏幕中打印出:Hello, world!

Common Lisp[编辑]

(format t "hello, world!")
;; 在屏幕中打印出:hello, world!

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

;; 调用函数:
(hello-world)
;; 在屏幕中打印出:hello, world!
;; 并以NIL作为函数的返回值

Clojure[编辑]

(print "hello, world!")
;; 在屏幕中打印出:hello, world!

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

;; 调用函数:
(hello-world)
;; 在屏幕中打印出:hello, world!
;; 并以nil作为函数的返回值

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"

参见[编辑]

参考文献[编辑]

外部链接[编辑]