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"
參見編輯
參考文獻編輯
外部連結編輯