前言
對於撰寫程式來說,編譯器 (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.lisp 和 manual.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 中指定 :sources
為 manual.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。
繼續深入
如果你覺得這篇 Common Lisp 的文章對你有幫助,可以看看「跨平台 Common Lisp 程式設計」電子書。這本書有更多關於 Common Lisp 程式設計的內容: