美思 [Common Lisp] 程式設計教學:Roswell 入門

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在閱讀 Common Lisp 的學習資料時,有時候會讀到 Roswell 這套軟體。由於 Roswell 需要額外的設置,會讓 Common Lisp 學習者感到困惑:到底 Roswell 是不是必要的?

著眼於這個議題,筆者寫了一篇有關 Roswell 的文章,讓讀者了解 Roswell 的思維及使用方式。

Roswell 的設計理念

Roswell 是 Common Lisp 管理軟體。除了管理 Common Lisp 實作品外,在這幾年中,Roswell 持續地發展、擴展其功能。目前 Roswell 可以做到以下任務:

  • 安裝並切換 Common Lisp 實作品
  • 安裝基於 Roswell 的 Common Lisp 應用程式
  • 撰寫基於 Roswell 的 Common Lisp 腳本
  • 將 Roswell 腳本進一步編譯為執行檔
  • 發佈基於 Roswell 的 Common Lisp 應用程式
  • 利用 Roswell 做持續整合

這個軟體專案原本的目的是第一項,其他項目則是逐漸發展出來的功能。由此可看出,Roswell 是相當完整的 Common Lisp 管理軟體。藉由 Roswell 所提供的功能,讓 Common Lisp 躍升為全方位的現代化程式語言。

為什麼 Roswell 不是很成功的專案?

筆者覺得 Roswell 的設計理念很好,但 Roswell 使用的議題其實是非戰之罪,這些問題的根源來自於 Common Lisp 實作品的歧異性。

當初 Common Lisp 標準發佈時,留下了許多空間,造成各個 Common Lisp 實作品對於標準未規範的部分各自為政。由於時空背景改變,Common Lisp 變成利基語言,應該也不會將 Common Lisp 標準改版了。

Roswell 封裝的是呼叫 Common Lisp 實作品的命令列指令,Common Lisp 程式碼 (變數、函式、巨集等) 間的歧異並未因而消除。所以,Common Lisp 程式設計者仍然要去查詢 Common Lisp 實作品的使用手冊,利用條件編譯來封裝 Common Lisp 實作品間的歧異。

另外,Roswell 使用時仍然會碰到一些小錯誤。有經驗的 Roswell 使用者應該會知道如何排除問題,但對 Common Lisp 初學者來說,Roswell 的錯誤會讓人誤以為是 Common Lisp 本身的錯誤,降低初學者學習這個語言的意願。

目前 Roswell 主要是針對 Unix 來設計。雖然在 Windows 上也可使用 Roswell,但功能面會和 Unix 版本的 Roswell 有些落差。例如,Roswell 腳本會利用到 POSIX shell 的特性,在 Windows 上就無法充份發揮。

安裝 Roswell

Windows

這裡下載預編好的 Roswell 即可。請自行將 Roswell 的本地路徑加到 PATH 變數。

macOS

使用 Homebrew 來安裝 Roswell:

$ brew install roswell

GNU/Linux

Debian 系的 GNU/Linux 發行版可到這裡下載預編好的 DEB 套件。

其他的 GNU/Linux 發行版沒有預編好的套件,得自行從原始碼編譯。請下載以下軟體:

  • GCC
  • Make
  • Automake
  • zlib
  • libcurl

以 openSUSE 為例,需使用以下指令安裝相關套件:

$ sudo zypper install gcc make automake zlib-devel libcurl-devel

下載 Roswell 的原始碼並將其解壓縮,然後移動工作目錄:

$ wget -c https://github.com/roswell/roswell/archive/v20.04.14.105.tar.gz
$ tar xf v20.04.14.105.tar.gz
$ cd roswell-20.04.14.105

參考以下指令來編譯及安裝 Roswell:

$ sh bootstrap
$ ./configure --prefix=$HOME/opt/roswell
$ make
$ make install

$HOME/opt/roswell/bin 加入 PATH 變數後,即可使用 Roswell。

第一次使用 Roswell

第一次執行 Roswell 時,不要加參數:

$ ros

這時候 Roswell 會自動下載預編好的 SBCL 和 QuickLisp,並建立自己的發行版 (distribution) (註) 。該發行版的位置在家目錄。Unix 上相當於 $HOME/.roswell ,Windows 上相當於 %USERPROFILE%\.roswell

(註) Common Lisp 的發行版是指 Common Lisp 函式庫的集中處,並使用 QuickLisp 等函式庫管理軟體來自動化管理。

另外,請手動將 Roswell 發行版的 bin/ 子目錄加入 PATH 變數,因為 Roswell 所安裝的 Common Lisp 應用程式會集中到該目錄。在 Unix 上相當於 $HOME/.roswell/bin ,在 Windows 上相當於 %USERPROFILE%\.roswell\bin

之後就可以正常地使用 Roswell。

安裝和使用 Common Lisp 實作品

輸入以下指令可以秀出可用的 Common Lisp 實作品:

$ ros install

例如,用以下指令安裝 Clozure CL:

$ ros install ccl-bin

其實 Roswell 中安裝 Common Lisp 實作品的指令還蠻容易失敗的。筆者在撰寫這篇文章的時候 (西元 2020 年四月下旬),安裝 Clozure CL 12.0 會失敗,得回頭裝 Clozure CL 1.11.5。可參考以下指令:

$ ros install ccl-bin/1.11.5

若本地的 Roswell 發行版有安裝多個 Common Lisp 發行版,可用 ros list installed 指令檢查,再用 ros use 指令切換 Common Lisp 發行版。參考以下指令:

$ ros use ccl-bin

在免費的 Common Lisp 發行版中,SBCL 還是最穩定的。如果使用其他的 Common Lisp 發行版時出了問題,可以試著切回 SBCL:

$ ros use sbcl-bin

從原始碼編譯 Common Lisp 實作品

除了安裝預編好的 Common Lisp 實作品外,Roswell 還支援從原始碼編譯 Common Lisp 實作品。例如,用以下指令重編 SBCL:

$ ros install sbcl

注意 sbclsbcl-bin 是不同的版本。前者會從原始碼編譯 SBCL,後者則是直接下載預編的 SBCL。

筆者在 Windows 上測試這個指令時,Roswell 甚至自動下載了一套完整的 MSYS2,然後才開始編譯 SBCL,所以多花了點時間。

Roswell 這項特性只是自動化編譯 Common Lisp 實作品的過程,會不會編譯成功還是得看宿主系統的條件而定。

安裝和使用 Common Lisp 應用程式

除了管理 Common Lisp 實作品外,Roswell 還可以用來管理 Common Lisp 應用程式。例如,以下程式用來安裝 Clack (註)

(註) 基於 Common Lisp 的網頁框架。

$ ros install clack

安裝好後,命令列環境中會出現 clackup 指令。該指令本身也是 Roswell 腳本,詳見下文。

Clack 本身是 QuickLisp 上有登記的函式庫,所以可以用 Roswell 直接安裝。Roswell 也可以直接從 GitHub 安裝 Common Lisp 應用程式。以下範例指令摘自 Roswell 官方維基:

$ ros install fukamachi/prove

由於 Roswell 所下載的 Common Lisp 本身是 Roswell 腳本,而這些腳本是基於 POSIX shell 設計的,在 Windows 上使用會有問題。目前的解法是自己加 Batch 腳本。例如,以下範例程式碼是 clackup 的 Batch 命令稿:

rem clackup.bat
@echo off

rem Check whether Roswell is available.
ros --version >nul 2>&1 || (
  echo Roswell is not available on the system
  exit /B 1
)

rem Get the path of the script.
set rootdir=%~dp0

rem Call the target Roswell script.
ros %rootdir%clackup.ros %*

將這個腳本放在 Roswell 發行版的 bin/ 子目錄即可。

其實,Roswell 應該讓生成 Batch 命令稿的動作自動化,反正這種 Batch 命令稿的內容很制式化,就是用 Roswell 呼叫某個 Roswell 腳本。Windows 版本的 Node.js 在全域安裝命令列工具時就會自動加 Batch 命令稿了,這個功能應該有機會實作出來。

啟動 REPL 環境

若要使用 Common Lisp 的 REPL 環境,使用 ros run 指令即可啟動 REPL 環境。實際使用的 REPL 環境會根據 Roswell 當下指定的 Common Lisp 實作品而定。

移除 Roswell 發行版

如果因為某些原因,把 Roswell 玩爛了,可以移除 Roswell 發行版,不太需要移除 Roswell 本身。該發行版位於家目錄下。Unix 上相當於 $HOME/.roswell ,Windows 上相當於 %USERPROFILE%\.roswell 。只要把該目錄整個移除即可,Roswell 不會在系統上留下設定檔或機碼。

移除 Roswell 發行版後,Roswell 就回到全新的狀態。下次執行 Roswell 時會再度初始化。細節請見上文。

(進階) 撰寫 Roswell 腳本

除了使用現有的軟體或函式庫外,程式設計者也可以撰寫基於 Roswell 的腳本。甚至進一步把 Roswell 腳本編譯成執行檔。

雖然 Roswell 腳本在本質上是 POSIX shell 腳本,但 Roswell 腳本有特殊的樣板程式碼,故請用 Roswell 來生成 Roswell 腳本,不要自已手刻。

假定我們要寫 Hello World 程式,使用 Roswell 生成 Roswell 腳本:

$ ros init hello

這時候會生成 hello.ros ,即為一個可用的 Roswell 腳本模板。

在開始撰寫 Roswell 腳本前,先來看一下 Roswell 腳本的模板:

#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
  (ros:ensure-asdf)
  #+quicklisp(ql:quickload '() :silent t)
  )

(defpackage :ros.script.hello.3796436398
  (:use :cl))
(in-package :ros.script.hello.3796436398)

(defun main (&rest argv)
  (declare (ignorable argv)))
;;; vim: set ft=lisp lisp:

從這段程式碼可見,其實 Roswell 腳本就是 POSIX shell 腳本。但 Roswell 腳本加上了特殊的模板,這裡用到了 shell wrapper 的技巧。

當系統執行此腳本時,會先以 POSIX shell 來執行該腳本。執行到 exec 指令時,會由 Roswell 接手處理這個腳本。在 Roswell 接手後,原本的 exec 指令的部分會藏在 Common Lisp 的多行註解中,不會執行到。實際的效果就是執行一個 Common Lisp 腳本。

在 Roswell 腳本中,main 函式是 Roswell 新增的功能,而非 Common Lisp 原本的特性。Roswell 腳本的 main 函式可以充當應用程式的主函式 (main function),做為應用程式的進入點。

我們將這個 Roswell 腳本改寫成 Hello World 程式:

#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(defun main (&rest argv)
  (declare (ignorable argv))
  (write-line "Hello World")
  (finish-output))
;;; vim: set ft=lisp lisp:

我們刻意把非必要的部分移除,只留下最精簡的 Hello World 程式碼。

使用 Roswell 即可執行此腳本:

$ ros hello.ros
Hello World

如果想把 Roswell 腳本編譯成執行檔,就用 ros build 指令:

$ ros build hello.ros
$ ./hello
Hello World

在 Windows 上,同樣可以撰寫和執行 Roswell 腳本,也可以把 Roswell 腳本編譯成執行檔。但受限於 Windows 的特性,無法直接把 Roswell 腳本當成命令列指令來用。

(進階) 部署 Roswell 應用程式

Roswell 是相當全面的 Common Lisp 管理軟體,也支援部署基於 Roswell 的應用程式。若想讓 Common Lisp 專案支援 Roswell,在該專案中建立 roswell/ 子目錄,並將 Roswell 腳本寫在該目錄即可。

Clack 為例。在 roswell/ 子目錄下就有 clackup.ros 腳本。所以在安裝完 Clack 後,會多出 clackup 指令。

關於作者

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

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