一起來學習 Lisp 編程語言吧!
早在 1958 年,Lisp 就被發明出來了,它是世界上第二古老的計算機編程語言(LCTT 譯註:最古老的編程語言是 Fortran,誕生於 1957 年)。它有許多現代的衍生品,包括 Common Lisp、Emacs Lisp(Elisp)、Clojure、Racket、Scheme、Fennel 和 GNU Guile 等。
那些喜歡思考編程語言的設計的人,往往都喜歡 Lisp,因為它的語法和數據有著相同的結構:Lisp 代碼實際上是 一個列表的列表 ,它的名字其實是 「 列表處理 」 的簡寫。而那些喜歡思考編程語言的美學的人,往往都討厭 Lisp,因為它經常使用括弧來定義範圍;事實上,編程界也有一個廣為流傳的笑話:Lisp 代表的其實是 「大量煩人的多餘括弧」 。
不管你是喜歡還是討厭 Lisp 的設計哲學,你都不得不承認,它都是一門有趣的語言,過去如此,現在亦然(這得歸功於現代方言 Clojure 和 Guile)。你可能會驚訝於在任何特定行業的大代碼庫中潛伏著多少 Lisp 代碼,因此,現在開始學習 Lisp,至少熟悉一下它,不失為一個好主意。
安裝 Lisp
Lisp 有很多不同的實現。比較流行的開源版本有 SBCL、GNU Lisp 和 GNU Common Lisp(GCL)。你可以使用發行版的包管理器安裝它們中的任意一個,在本文中,我是用的是 clisp
(LCTT 譯註:也就是 GNU Lisp,一種 ANSI Common Lisp 的實現)。
以下是在不同的 Linux 發行版中安裝 clisp
的步驟。
在 Fedora Linux 上,使用 dnf
:
$ sudo dnf install clisp
在 Debian 上,使用 apt
:
$ sudo apt install clisp
在 macOS 上,使用 MacPorts 或者 Homebrew:
# 使用 MacPorts
$ sudo port install clisp
# 使用 Homebrew
$ brew install clisp
在 Windows 上,你可以使用 clisp on Cygwin 或者從 gnu.org/software/gcl 上下載 GCL 的二進位文件。
雖然我使用 clisp
命令來運行 Lisp 代碼,但是本文中涉及到的大多數語法規則,對任何 Lisp 實現都是適用的。如果你選擇使用一個不同的 Lisp 實現,除了用來運行 Lisp 代碼的命令會和我不一樣外(比如,你可能要用 gcl
或 sbcl
而不是 clisp
),其它的所有東西都是相同的。
列表處理
Lisp 源代碼的基本單元是 「 表達式 」,它在形式上是一個列表。舉個例子,下面就是一個列表,它由一個操作符(+
)和兩個整數(1
和 2
)組成:
(+ 1 2)
同時,它也是一個 Lisp 表達式,內容是一個符號(+
,會被解析成一個加法函數)和它的兩個參數(1
和 2
)。你可以在 Common Lisp 的互動式環境(即 REPL)中運行該表達式和其它表達式。如果你熟悉 Python 的 IDLE,那麼你應該會對 Lisp 的 REPL 感到親切。(LCTT 譯註:REPL 的全稱是 「Read-Eval-Print Loop」,意思是 「『讀取-求值-輸出』循環」,這個名字很好地描述了它的工作過程。)
要進入到 REPL 中,只需運行 Common Lisp 即可:
$ clisp
[1]>
在 REPL 提示符中,嘗試輸入一些表達式:
[1]> (+ 1 2)
3
[2]> (- 1 2)
-1
[3]> (- 2 1)
1
[4]> (+ 2 3 4)
9
函數
在了解了 Lisp 表達式的基本結構後,你可以使用函數來做更多有用的事。譬如,print
函數可以接受任意數量的參數,然後把它們都顯示在你的終端上,pprint
函數還可以實現格式化列印。還有更多不同的列印函數,不過,pprint
在 REPL 中的效果就挺好的:
[1]> (pprint "hello world")
"hello world"
[2]>
你可以使用 defun
函數來創建一個自定義函數。defun
函數需要你提供自定義函數的名稱,以及它接受的參數列表:
[1]> (defun myprinter (s) (pprint s))
MYPRINTER
[2]> (myprinter "hello world")
"hello world"
[3]>
變數
你可以使用 setf
函數來在 Lisp 中創建變數:
[1]> (setf foo "hello world")
"hello world"
[2]> (pprint foo)
"hello world"
[3]>
你可以在表達式里嵌套表達式(就像使用某種管道一樣)。舉個例子,你可以先使用 string-upcase
函數,把某個字元串的所有字元轉換成大寫,然後再使用 pprint
函數,將它的內容格式化列印到終端上:
[3]> (pprint (string-upcase foo))
"HELLO WORLD"
[4]>
Lisp 是動態類型語言,這意味著,你在給變數賦值時不需要聲明它的類型。Lisp 默認會把整數當作整數來處理:
[1]> (setf foo 2)
[2]> (setf bar 3)
[3]> (+ foo bar)
5
如果你想讓整數被當作字元串來處理,你可以給它加上引號:
[4]> (setf foo "2")
"2"
[5]> (setf bar "3")
"3"
[6]> (+ foo bar)
*** - +: "2" is not a number
The following restarts are available:
USE-VALUE :R1 Input a value to be used instead.
ABORT :R2 Abort main loop
Break 1 [7]>
在這個示例 REPL 會話中,變數 foo
和 bar
都被賦值為加了引號的數字,因此,Lisp 會把它們當作字元串來處理。數學運算符不能夠用在字元串上,因此 REPL 進入了調試器模式。想要跳出這個調試器,你需要按下 Ctrl+D
才行(LCTT 譯註:就 clisp
而言,使用 quit
關鍵字也可以退出)。
你可以使用 typep
函數對一些對象進行類型檢查,它可以測試對象是否為某個特定數據類型。返回值 T
和 NIL
分別代表 True
和 False
。
[4]> (typep foo 'string)
NIL
[5]> (typep foo 'integer)
T
string
和 integer
前面加上了一個單引號('
),這是為了防止 Lisp(錯誤地)把這兩個單詞當作是變數來求值:
[6]> (typep foo string)
*** - SYSTEM::READ-EVAL-PRINT: variable STRING has no value
[...]
這是一種保護某些術語(LCTT 譯註:類似於字元串轉義)的簡便方法,正常情況下它是用 quote
函數來實現的:
[7]> (typep foo (quote string))
NIL
[5]> (typep foo (quote integer))
T
列表
不出人意料,你當然也可以在 Lisp 中創建列表:
[1]> (setf foo (list "hello" "world"))
("hello" "world")
你可以使用 nth
函數來索引列表:
[2]> (nth 0 foo)
"hello"
[3]> (pprint (string-capitalize (nth 1 foo)))
"World"
退出 REPL
要結束一個 REPL 會話,你需要按下鍵盤上的 Ctrl+D
,或者是使用 Lisp 的 quit
關鍵字:
[99]> (quit)
$
編寫腳本
Lisp 可以被編譯,也可以作為解釋型的腳本語言來使用。在你剛開始學習的時候,後者很可能是最容易的方式,特別是當你已經熟悉 Python 或 Shell 腳本 時。
下面是一個用 Common Lisp 編寫的簡單的「擲骰子」腳本:
#!/usr/bin/clisp
(defun roller (num)
(pprint (random (parse-integer (nth 0 num))))
)
(setf userput *args*)
(setf *random-state* (make-random-state t))
(roller userput)
腳本的第一行注釋(LCTT 譯註:稱之為「 釋伴 」)告訴了你的 POSIX 終端,該使用什麼可執行文件來運行這個腳本。
roller
函數使用 defun
函數創建,它在內部使用 random
函數來列印一個偽隨機數,這個偽隨機數嚴格小於 num
列表中下標為 0 的元素。在腳本中,這個 num
列表還沒有被創建,不過沒關係,因為只有當腳本被調用時,函數才會執行。
接下來的那一行,我們把運行腳本時提供的任意參數,都賦值給一個叫做 userput
的變數。這個 userput
變數是一個列表,當它被傳遞給 roller
函數後,它就會變成參數 num
。
腳本的倒數第二行產生了一個「隨機種子」。這為 Lisp 提供了足夠的隨機性來生成一個幾乎隨機的數字。
最後一行調用了自定義的 roller
函數,並將 userput
列表作為唯一的參數傳遞給它。
將這個文件保存為 dice.lisp
,並賦予它可執行許可權:
$ chmod +x dice.lisp
最後,運行它,並給它提供一個數字,以作為它選擇隨機數的最大值:
$ ./dice.lisp 21
13
$ ./dice.lisp 21
7
$ ./dice.lisp 21
20
看起來還不錯!
你或許注意到,你的模擬骰子有可能會是 0,並且永遠達不到你提供給它的最大值參數。換句話說,對於一個 20 面的骰子,這個腳本永遠投不出 20(除非你把 0 當作 20)。有一個簡單的解決辦法,它只需要用到在本文中介紹的知識,你能夠想到嗎?
學習 Lisp
無論你是想將 Lisp 作為個人腳本的實用語言,還是為了助力你的職業生涯,抑或是僅僅作為一個有趣的實驗,你都可以去看看一年一度(LCTT 譯註:應該是兩年一度)的 Lisp 遊戲果醬 ,從而收穫一些特別有創意的用途(其中的大多數提交都是開源的,因此你可以查看代碼以從中學習)。
Lisp 是一門有趣而獨特的語言,它有著不斷增長的開發者用戶群、足夠悠久的歷史和新興的方言,因此,它有能力讓從事各個行業的程序員都滿意。
via: https://opensource.com/article/21/5/learn-lisp
作者:Seth Kenlon 選題:lkxed 譯者:lkxed 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive