位元詩人 [Common Lisp] 程式設計教學:相關的開發工具

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

對於撰寫程式來說,編譯器 (compiler) 和編輯器 (editor) 算是最基本的開發工具。一開始先熟悉這兩種工具就可以開始學習程式設計。

除此之外,各式各樣的開發工具在不同面向協助程式設計者開發應用程式。由於這些工具在學習程式設計的初期不會馬上用到,一開始不用急著全部學起來。以 Common Lisp 來說,先會用 QuickLisp 安裝社群函式庫就夠了。其餘的開發工具行有餘力再慢慢學習即可。

用 QuickLisp 安裝 Common Lisp 函式庫

QuickLisp 是 Common Lisp 的函式庫管理程式。這個程式對於 Common Lisp 的現代化具有相當的貢獻。透過 QuickLisp,Common Lisp 程式設計者可以快速地安裝社群函式庫。當程式設計者能夠方便地管理函式庫,社群資源就可以累積,讓更多程式設計者使用 Common Lisp。在正向循環中使這個語言更加茁壯。

全域安裝 QuickLisp

在預設情形下,QuickLisp 會以全域安裝 (global installation) 的方式來安裝自身及其他函式庫。我們在本節展示這個流程。

到 QuickLisp 的官網下載 quicklisp.lisp ,這是一個 Common Lisp 命令稿。

開啟終端機環境,將工作目錄移到 quicklisp.lisp 命令稿所在的位置後,開啟 Common Lisp 的 REPL 互動式環境。我們會在 REPL 環境中安裝 QuickLisp。此處以 SBCL 為例。經筆者實測,對 Clozure CL、Armed Bear CL 等 Common Lisp 實作品也適用。

在 REPL 環境中用 load 函式載入 quicklisp.lisp 命令稿:

* (load "quicklisp.lisp")

這時候還不算是安裝,只是載入 QuickLisp 所提供的命令稿。按照 QuickLisp 的提示訊息輸入相對應的指令來安裝 QuickLisp:

* (quicklisp-quickstart:install)

由於我們沒有使用額外的參數,QuickLisp 會安裝到預設的位置。預設的位置是在家目錄下建立 quicklisp/ 子目錄。在 Windows 中,會像是 C:\Users\user\quicklisp ,在 Unix 中,則像是 /home/user/quicklisp

由於 QuickLisp 是後設的功能,Common Lisp 實作品不會自動去讀取 QuickLisp 所在的目錄。解決的方式是在 Common Lisp 的起始設定檔 (init file) 中加入相關設定。QuickLisp 已經把這個任務自動化了。輸入以下指令來自動加入相關設定:

* (ql:add-to-init-file)

之後在啟動 Common Lisp 編譯器時,會自動啟動 QuickLisp,Common Lisp 實作品就有自動安裝函式庫的功能。

日後要安裝函式庫時,同樣再進 REPL 環境,使用 ql:quickload 指令即可安裝函式庫。以下指令用 QuickLisp 安裝 Parenscript

* (ql:quickload "parenscript")

在安裝 QuickLisp 後,每次啟動 Common Lisp 編譯器的速度會變慢一點,畢竟我們在啟動 Common Lisp 編譯器時多做了載入 QuickLisp 這個動作。如果不想繼續使用 QuickLisp,將 quicklisp/ 目錄整個移除即可。QuickLisp 不會在系統上留下機碼或設定檔,算是蠻環保的 (green) 軟體。

(選擇性) 局部安裝 QuickLisp

除了前一節的使用方式外,QuickLisp 也可以安裝在特定專案內。透過局部安裝,該專案就不必依賴宿主系統上的函式庫,達成自給自足的狀態。

在本節中,我們以實例來說明局部安裝 QuickLisp 的使用方式。cl2js 是一個命令列小工具,該工具的用途是將 Common Lisp 程式碼轉為等效的 JavaScript 程式碼。該工具依賴 Parenscript 來執行實際的轉換過程。但我們不想讓該工具的使用者手動安裝 Parenscript,所以我們採用局部安裝的方式。

Common Lisp 實作品是混合編譯和直譯的軟體,我們利用自動化腳本協助專案使用者將該專案自動編譯成執行檔 (native executable)。自動化編譯的原理是在專案內預先擺放 QuickLisp 的命令稿,所有的相依性都由該命令稿自動局部安裝到專案內,然後連同主程式和相依函式庫一起編譯成執行檔。

以 SBCL 為例,自動化編譯的指令如下:

sbcl --load quicklisp.lisp \
     --eval "(quicklisp-quickstart:install :path \"./quicklisp\")" \
     --eval "(ql:quickload \"parenscript\")" \
     --load cl2js.lisp \
     --eval "(compile-program \"cl2js\" #'main)"

quicklisp.lisp 可利用系統的命令列工具自動下載,而 cl2js.lisp 則是專案的主程式。我們利用 SBCL 可多次呼叫指令的特性,把編譯流程寫在指令中。讀者可以注意一下局部安裝 QuickLisp 的指令。

用 ASDF (Another System Definition Facility) 管理 Common Lisp 專案

ASDF 是自動編譯軟體,相當於 Common Lisp 版本的 Make。Common Lisp 程式設計者較少直接使用 ASDF,多是搭配 QuickLisp 使用。

安裝 ASDF

QuickLisp 內部就會使用 ASDF,所以不太需要自已手動安裝。如果想要自行安裝,先按照上文的說明安裝 QuickLisp 後,在 REPL 環境中輸入以下指令:

* (ql:quickload "asdf")

然後引入 ASDL 函式庫:

* (require "asdf")

確認一下 ASDF 的版本:

* (asdf:asdf-version)
"3.3.1"

撰寫 ASDF 設定檔

對於 Common Lisp 函式庫 (註) 撰寫者來說,撰寫 ASDF 設定檔可讓函式庫使用者更便利地使用該函式庫。QuickLisp 會自動呼叫該函式庫所寫的 ASDF 設定檔。

(註) Common Lisp 將函式庫稱為 system。

以筆者自製的工具函式 cl-yautils 為例,其 ASDF 設定檔 cl-yautils.asd 位於專案的根目錄,其內容如下:

(defsystem "cl-yautils"
  :description "Yet another utility library for Common Lisp"
  :version "0.1.0"
  :author "Michelle Chen <user@example.com>"
  :license "MIT"
  :components ((:file "cl-yautils")))

雖然 ASDF 的設定檔也要用 Lisp 來寫,但這沒有什麼複雜的程式邏輯,只是把 Lisp 當成資料描述語言在寫而已。

用 cl-project 快速建立專案模板

Common Lisp 沒有規範軟體專案的架構,只要專案能順利運行、架構不要太混亂即可。像 cl-project 這類專案模板生成器的用途在於輔助 Common Lisp 程式設計者快速地建立可用的專案,而非 Common Lisp 專案的標準。

安裝 cl-project

先按照上文安裝 QuickLisp 後,在 REPL 環境中安裝 cl-project:

* (ql:quickload "cl-project")

安裝後,在 REPL 環境中載入 cl-project:

* (require "cl-project")

用 cl-project 建立專案模板

使用 make-project 指令即可建立專案模板。同樣在 REPL 環境下,以 myapp 專案為例:

* (cl-project:make-project #p"myapp")

這時候,cl-project 會在工作目錄下建立 myapp 目錄,並建立好相關的檔案。

如果想要預先填入一些專案相關的資訊,可以參考 cl-project 官網的範例指令:

* (cl-project:make-project #p"lib/cl-sample/"
  :author "Eitaro Fukamachi"
  :email "e.arrows@gmail.com"
  :license "LLGPL"
  :depends-on '(:clack :cl-annot))

這些內容都可以事後再補,一開始沒填寫也沒關係。

cl-project 只是輔助

事實上,筆者在學寫 Common Lisp 專案時,並未使用 cl-project。只要熟悉 Common Lisp 實作品的使用方式,再搭配一些命令列環境腳本,很容易就可以寫出 Common Lisp 專案。由於 Common Lisp 兼具編譯語言和直譯語言的特性,既可編譯出執行檔又可當命令稿使用,不像 C 或 C++ 等傳統的編譯語言依賴外部工具來管理專案。

對於不熟悉 Common Lisp 專案的讀者來說,cl-project 所建立的專案模板可當成模仿的對象,但不必受到 cl-project 的專案架構限制。專案設定檔只是輔助軟體建置的設定,而不是限制專案的枷鎖。

用 Codex 撰寫專案說明文件

為什麼要寫軟體文件?

對於程式設計師來說,現在是資訊爆炸的時代。不斷有新的語言 (programming language)、函式庫 (library)、框架 (framework)、開發工具 (development tool) 出現,現有的開發軟體也會更新。在這樣開發軟體競相出頭的年代,文件不良、缺乏範例的開發軟體很快就會被遺忘。所以,不要吝於花時間為自己的軟體專案寫文件。

然而,如果要從頭手刻網頁或 PDF 文件未免太辛苦了。許多程式語言都有輔助製作 API 文件的工具。以 Common Lisp 社群來說,可以用 Codex 製作具有現代感的線上文件。由於 Codex 會去掃函式庫原始碼,將其轉為相對應的網頁,程式碼和文件間可以保持一致。

安裝 Codex

在 REPL 環境中以 QuickLisp 安裝 Codex:

* (ql:quickload "codex")

之後,同樣在 REPL 環境中載入 Codex 即可使用:

* (require "codex")

使用 Codex 撰寫文件的實例

使用 Codex 的話,要在軟體專案中建立 docs/ 子目錄,並在該目錄下建立 manifest.lispmanual.scr 等文字檔案。

接下來,筆者以自製的工具函式 cl-yautils 為例,來看如何使用 Codex。

manifest.lisp 儲存 Codex 所需的元資訊 (metadata)。一個實際的 manifest.lisp 範例如下:

(:docstring-markup-format :scriba
 :systems (:cl-yautils)
 :documents ((:title "cl-yautils"
              :authors ("Michelle Chen")
              :output-format (:type :multi-html
                             :template :minima)
              :sources ("manual.scr"))))

請讀者不要照抄這個範例,而要根據自己實際的情況來修改。

由於我們在 manifest.lisp 中指定 :sourcesmanual.scr ,所以我們要另外建立一個同名的文字檔案。

manual.scr 使用的標記語言是 Scriba,但又加上 Codex 自己的延伸

筆者節錄自己寫的 manual.scr 如下:

@begin(section)
@title(Macro)

@cl:with-package[name="cl-yautils"](
@cl:doc(macro defined)
@cl:doc(type nullable)
@cl:doc(macro while)
)
@end(section)

如果想看更完整的範例,可看這裡

建議讀者搭配 Scriba 的語法說明一起看,很快就能理解 Scriba 文件的寫法。

編譯 Codex 文件

承上節,移動工作目錄到軟體專案的根目錄,開啟 SBCL 的 REPL 環境。先載入 Codex:

* (require "codex")

使用 document 指令即可編譯文件。同樣以 cl-yautils 為例:

* (codex:document :cl-yautils)

由於編譯文件是很機械化的動作,可以將其寫成腳本來自動化。以下是筆者在 Windows 下用的 Batch 命令稿:

sbcl --eval "(setf sb-impl::*default-external-format* :UTF-8)" ^
     --eval "(require \"codex\")" ^
     --eval "(codex:document :cl-yautils)" ^
     --eval "(quit)"

在 Windows 下建議設置輸出格式為 UTF-8。因為 Codex 產生的文件使用 UTF-8 編碼,但 Windows 的命令列環境的預設編碼並非 UTF-8,故我們手動設置 SBCL 的輸出格式。

編譯 Codex 文件時,Codex 必需要能搜尋到該函式庫。最簡單的方式就是將函式庫放在 QuickLisp 發行版的 local-projects/ 子目錄中。

用 Git 等版本控制系統管理專案

現在的軟體專案都會上 Git 等版本控制軟體,用來管理專案的版本。當軟體專案使用 Git 後,就有狀態的概念。例如,有時候程式碼寫爛了,可以用 git checkout 回復到原本的狀態。即使是個人的 side project,也可以試著用 Git 管理。

限於篇幅,我們不在本文介紹 Git 的用法。讀者可以自己上網找資料,像是 Pro Git (中文版) 或是為你自己學 Git

關於作者

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

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