LISP
| 此条目需要补充更多来源。 (2019年3月2日) |
Lisp(历史上拼写为LISP)是具有悠久历史的计算机编程语言家族,有独特和完全括号的前缀符号表示法。起源于西元1958年,是现今第二悠久而仍广泛使用的高阶编程语言。只有FORTRAN编程语言比它更早一年。Lisp编程语族已经演变出许多种方言。现代最著名的通用编程语种是Clojure、Common Lisp和Scheme。
| 编程范型 | 多范型:函数式,过程式,反射式,元编程 |
|---|---|
| 设计者 | 约翰·麦卡锡 |
| 实作者 | 史帝芬·罗素, Timothy P. Hart和Mike Levin |
| 发行时间 | 1958年 |
| 型态系统 | 动态类型,强类型 |
| 衍生副语言 | |
| Arc, AutoLISP, Clojure, Common Lisp, Emacs Lisp, ISLISP, Logo, newLISP, Racket, Scheme, SKILL | |
| 启发语言 | |
| IPL | |
| 影响语言 | |
| CLU, Dylan, Falcon, Forth, Haskell, Io语言, Ioke, JavaScript, Lua, LPC, MDL,ML, Nu, OPS5, Perl,POP-2/11, Python, REBOL, Ruby, Smalltalk, Wolfram语言 | |
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, Scheme,Racket以及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等命令型程序语言和Java、C#、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"
参见编辑
参考文献编辑
外部链接编辑