位元詩人 [Common Lisp] 程式設計教學:介紹

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

歷久彌新的古典語言

Lisp 是資訊界上古三大神兵 (Fortran、Lisp、COBOL) 之一,世界第二古老的高階程式語言。這個語言在長期的演進過程中出現過許多方言 (dialect),包括我們要介紹的 Common Lisp。直到現在仍然有一些軟體專案使用 Lisp 家族語言來實作。

雖然 Lisp 是老語言,在當時實作出許多開創性的 (innovative) 程式語言概念,這些概念由後來的程式語言所吸收和利用。像是動態型態 (dynamic typing)、高階函式 (higher-order function)、遞迴 (recursion)、垃圾回收 (garbage collection)、REPL 環境等特性最早就是實作在 Lisp 上。

但早期電腦的運算資源相對受限,所以 Lisp 沒有受到廣泛的採用,只有在人工智慧 (artificial intelligence) 等少數研究領域會用到。近年來電腦的運算資源改善許多,所以 Lisp 有復甦的跡象。此外,也出現 Clojure 等新興 Lisp 方言,為 Lisp 帶來新氣象。

由於 Lisp 算是相對冷門的語言,學習 Lisp 並不是為了就學、就業等實際面的考量。而是藉由學習 Lisp 體驗不同的範式 (paradigms),以拓展對程式設計的視野。

Lisp 的主要方言 (Dialect)

Lisp 的方言很多,初學者會覺得眼花瞭亂。比較好的方式是先專注在其中一個 Lisp 方言上。以下是幾個主要的 Lisp 方言:

  • Common Lisp:主流的 Lisp 方言,支援多種範式 (paradigms)
  • Scheme:另一個主流的 Lisp 方言,以精簡的 (minimal) 特性著名於世
  • Racket:一個基於 Scheme 的 Lisp 方言,但不完全相容於 Scheme
  • Emacs Lisp:Emacs 編輯器所用的 Lisp 方言
  • Clojure:運行在 Java 平台的 Lisp 方言

由於每種 Lisp 方言間不完全相容,建議一次先專心學一種,直到熟練後才換學另一種。雖然我們會以方言來稱呼不同的 Lisp,最好還是把不同 Lisp 方言視為不同的程式語言來學習比較好。

本系列文章所要學習的 Lisp 方言是 Common Lisp。

Common Lisp 的歷史

Common Lisp 是一套語言標準,其目的是為了整合數個不相容的 MacLisp 方言。所以 Common Lisp 本身沒有官方實作品,而由不同開發團隊提供 Common Lisp 的實作品。

整合標準本來就是一個困難的過程。Common Lisp 本身的目標是提供可攜 (portable)、一致 (consistent)、具有表達力的 (expressive) Lisp 方言,而且儘可能和先前的 Lisp 方言相容。但不同 Common Lisp 實作品間仍有一些細微的差異。不過,只要小心地處理不相容的部分,Common Lisp 程式碼仍然是可攜的。

有關 Common Lisp 的迷思 (Myth)

由於 Common Lisp 使用者少、社群資源少、學習資源缺乏,的確會讓程式設計者不太想學。本節整理出一些對 Common Lisp 的迷思,讓我們重新看待這個語言。

Common Lisp 已經沒人在用了

現在 Common Lisp 的確算是利基語言 (niche programming language),在幾個主要的程式語言排名皆排不進主流語言之列。但 Common Lisp 並非過時的 (obsolete) 語言,現在仍然有一些軟體專案以 Common Lisp 來實作,其中不乏商業公司的專案。

Common Lisp 只是電腦玩家使用的語言

網路上的確有數個免費的 Common Lisp 實作品,但也有一些商用方案:

  • Clozure Associates:提供 Common Lisp 相關的開發或諮詢服務
  • LispWorks:商用 Common Lisp 整合式開發環境
  • Allergo Common Lisp:另一個商用 Common Lisp 整合式開發環境

所以 Common Lisp 並不僅是技客 (geek) 或玩家 (power user) 所用的程式語言。

Common Lisp 是運行緩慢的純直譯語言

早期的 Lisp 的確是直譯語言,但 Common Lisp 中有數個實作品都可以把 Lisp 程式碼編譯成機械碼。雖然 Common Lisp 編譯器所編譯出來的機械碼不太會比 C 或 C++ 編譯器所編譯出來的機械碼快,但至少會比 Python 或 Ruby 等採用直譯的程式來得有效率。

筆者先前有撰文說明程式語言對能源運用的影響,在該篇文章的原引用論文所介紹的語言中,Common Lisp (註) 表現算是中上的。

(註) 使用 SBCL (Steel Bank Common Lisp)。

Common Lisp 是動態型態語言,無法寫出穩固的程式

Common Lisp 的確是動態型態語言,但可藉由 declarecheck-type 等指令在程式中加入型態宣告。在加入型態宣告後,Common Lisp 編譯器會協助程式設計者抓出程式中型態錯誤的部分,實際上的效果和靜態型態語言相差無幾。

更棒的是,由於 declarecheck-type 等指令是選擇性的,Common Lisp 的型態是可動可靜的。程式設計者可依據自己的需求選擇最適合的撰碼方式。

Common Lisp 程式碼只是充斥著中括號的無意義符號

S 表達式 (s-expression) 是 Lisp 家族語言的一大特色,但也給人 Lisp 難以撰寫的感覺。

如果我們仍然像個苦行僧,使用完全沒有輔助功能的 Notepad 來撰寫程式,每種程式語言看起來都難度倍增。反之,若我們搭配合適的編輯器,這些程式編輯器會協助我們檢查中括號是否成對,撰寫 Lisp 就不會那麼困難。

時間就是金錢。學習用合適的工具來改善開發流程,甚至自己寫工具來改善工作效率,也是學習程式設計的一環。

Common Lisp 不支援中日韓等多國文字

其實很多 Common Lisp 實作品都支援統一碼 (unicode),所以 Common Lisp 的確能處理多國文字。像是以下的小程式可以在終端機正確地印出中日韓文字:

(defun main ()
  (write-line "你好,世界")
  (write-line "こんにちは世界")
  (write-line "안녕 세상")
  (quit))

(main)

註:在 SBCL 上可能需要額外的設置,詳見後文。

Common Lisp 無法用來做圖形介面程式

實際上是可以的,常見的 binding 有 Tk、Qt、GTK、IUP、Nuklear 等。詳情請看這裡

常見的 Common Lisp 實作品

Common Lisp 本身是語言標準 (language standard),並沒有所謂的官方實作品,而是透過不同實作品來實踐該標準的特性。

以下是免費的 Common Lisp 實作品:

  • SBCL (Steel Bank Common Lisp):高效率的 Common Lisp 實作品
  • Clozure CL:商用軟體,但免費且開放原始碼
  • Embeddable CL:Common Lisp 轉 C 的轉譯器
  • ABCL (Armed Bear Common Lisp):運行在 Java 平台的 Common Lisp 實作品
  • Clasp:和 C++ 整合的 Common Lisp 實作品
  • CLISP:GNU 計畫項目之一

以下是商業的 Common Lisp 實作品 (加整合式開發環境):

  • Allegro CL
  • LispWorks

由於不同 Common Lisp 實作品間會有一些細微的差異,最好選定實作品後就持續使用,才不用一直修改程式碼。

選擇 Common Lisp 實作品

Common Lisp 的實作品眾多,要如何選擇呢?根據 Reddit 上的非正式調查 (survey),最多人使用的 Common Lisp 實作品是 SBCL,次多的是 Clozure CL (出處)。

使用較多人使用的 Common Lisp 實作品主要的好處在於第三方函式庫。這些函式庫的作者在測試函式庫時,通常會優先測試較普遍的 Common Lisp 實作品。當我們使用普遍的 Common Lisp 實作品時,支援度會比較好。

以下是 SBCL 的益處:

  • 可編譯出有效率的機械碼
  • 支援原生的執行緒 (native thread)
  • 同時對 Windows 和 Unix 友善
  • 免費且開放原始碼

以下是 Clozure CL 的益處:

  • 可編譯出有效率的機械碼
  • 快速編譯,代表更快的開發迭代
  • 支援原生的執行緒
  • 同時對 Windows 和 Unix 友善
  • 免費且開放原始碼
  • 必要時有商業支援

有些 Common Lisp 程式設計者會同時安裝多套 Common Lisp 實作品,這樣的目的是測試 Common Lisp 程式碼的相容性。不過,除非是要寫 Common Lisp 函式庫,這個作法不是必要的。

本系列文章的 Common Lisp 程式碼會分別以 SBCL 和 Clozure CL 測試。對於不相容的部分,則會分別列出等效的程式碼。

本系列文章所使用的記述方式

本系統文章會對 Common Lisp 程式碼加上語法高亮:

(defun main ()
  (write-line "Hello World")
  (quit))

(main)

根據 SBCL 的慣例,使用 * 表示 SBCL 的 REPL 互動性環境:

* (+ 3 4)

Clozure CL 則是使用 ? 表示其 REPL 環境:

? (+ 3 4)

根據 Unix 的慣例,使用 $ 表示命令列環境:

$ cd path/to/project

若使用 root 帳號,則改以 # 表示命令列環境:

# apt install gcc

根據 Windows 的慣例,使用 > 表示命令列環境。為了簡化,不列出工作目錄:

> cd path\to\project
關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。