前言
對於撰寫程式來說,編譯器 (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。