本頁使用了標題或全文手工轉換

LISP

維基百科,自由的百科全書
跳至導覽 跳至搜尋
Lisp
Lisplogo.png
編程範型 多範型函數式程序式反射式元程式設計
設計者 約翰·麥卡錫
實作者 史帝芬·羅素, Timothy P. Hart和Mike Levin
面市時間 1958年,​62年前​(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"

參見[編輯]

參考文獻[編輯]

外部連結[編輯]