Linux中國

一起來學習 Lisp 編程語言吧!

早在 1958 年,Lisp 就被發明出來了,它是世界上第二古老的計算機編程語言(LCTT 譯註:最古老的編程語言是 Fortran,誕生於 1957 年)。它有許多現代的衍生品,包括 Common Lisp、Emacs Lisp(Elisp)、Clojure、Racket、Scheme、Fennel 和 GNU Guile 等。

那些喜歡思考編程語言的設計的人,往往都喜歡 Lisp,因為它的語法和數據有著相同的結構:Lisp 代碼實際上是 一個列表的列表 a list of lists ,它的名字其實是 「 列表處理 LISt Processing 」 的簡寫。而那些喜歡思考編程語言的美學的人,往往都討厭 Lisp,因為它經常使用括弧來定義範圍;事實上,編程界也有一個廣為流傳的笑話:Lisp 代表的其實是 「大量煩人的多餘括弧」 Lots of Irritating Superfluous Parentheses

不管你是喜歡還是討厭 Lisp 的設計哲學,你都不得不承認,它都是一門有趣的語言,過去如此,現在亦然(這得歸功於現代方言 Clojure 和 Guile)。你可能會驚訝於在任何特定行業的大代碼庫中潛伏著多少 Lisp 代碼,因此,現在開始學習 Lisp,至少熟悉一下它,不失為一個好主意。

安裝 Lisp

Lisp 有很多不同的實現。比較流行的開源版本有 SBCLGNU LispGNU 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 代碼的命令會和我不一樣外(比如,你可能要用 gclsbcl 而不是 clisp),其它的所有東西都是相同的。

列表處理

Lisp 源代碼的基本單元是 「 表達式 expression 」,它在形式上是一個列表。舉個例子,下面就是一個列表,它由一個操作符(+)和兩個整數(12)組成:

(+ 1 2)

同時,它也是一個 Lisp 表達式,內容是一個符號(+,會被解析成一個加法函數)和它的兩個參數(12)。你可以在 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 會話中,變數 foobar 都被賦值為加了引號的數字,因此,Lisp 會把它們當作字元串來處理。數學運算符不能夠用在字元串上,因此 REPL 進入了調試器模式。想要跳出這個調試器,你需要按下 Ctrl+D 才行(LCTT 譯註:就 clisp 而言,使用 quit 關鍵字也可以退出)。

你可以使用 typep 函數對一些對象進行類型檢查,它可以測試對象是否為某個特定數據類型。返回值 TNIL 分別代表 TrueFalse

[4]> (typep foo 'string)
NIL
[5]> (typep foo 'integer)
T

stringinteger 前面加上了一個單引號('),這是為了防止 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 譯註:稱之為「 釋伴 shebang 」)告訴了你的 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 遊戲果醬 Game Jam ,從而收穫一些特別有創意的用途(其中的大多數提交都是開源的,因此你可以查看代碼以從中學習)。

Lisp 是一門有趣而獨特的語言,它有著不斷增長的開發者用戶群、足夠悠久的歷史和新興的方言,因此,它有能力讓從事各個行業的程序員都滿意。

via: https://opensource.com/article/21/5/learn-lisp

作者:Seth Kenlon 選題:lkxed 譯者:lkxed 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國