今まで M.Hiroi が Common Lisp の勉強で作成したプログラムをライブラリにまとめたものです。ネットを探せばもっと優れたライブラリが見つかると思いますが、せっかく作ったプログラムを有効利用したかったので、あえてライブラリを作ってみることにしました。
自作ライブラリは ASDF というツールを使うと簡単に管理することができます。なお、今回作成するライブラリは quicklisp に対応しておりません。あしからずご了承くださいませ。
ASDF (Another System Definition Facility) はライブラリのコンパイルやロードを行うためのツールです。多くの Common Lisp 処理系において、ASDF は標準で同梱されています。もちろん、SBCL にも ASDF は同梱されていて、(require :asdf) でロードすることができます。
* (require :asdf)
("ASDF" "asdf" "UIOP" "uiop")
* (asdf:asdf-version)
"3.3.1"
SBCL 2.1.11 に同梱されている ASDF のバージョンは 3.3.1 です。SBCL の設定ファイル ~/.sbclrc に (require :asdf) を書いておくと、SBCL の起動時に ASDF をロードすることができます。
ASDF では、ライブラリのことを「システム (system)」といいます。ASDF をロードすると、関数 require は ASDF のものに置き換えられます。そして、require を使ってシステムのコンパイルとロードを行うことができます。このとき、ASDF は標準で以下に示すパスから、指定されたシステムの定義ファイル (拡張子が .asd のファイル) を再帰的に探します。
たとえば、システム名が :sample とすると、上記パスの下に適当なサブディレクトリ (たとえば sample など) を作成し、その中にソースファイル sample.lisp と定義ファイル sample.asd を格納します。REPL で (require :sample) と入力すると、定義ファイル sample.asd に従って sample.lisp をコンパイルして、それをロードすることができます。
それでは実際に試してみましょう。最初にソースファイル sample.lisp にパッケージ sample を定義します。ASDF の場合、ソースファイルの拡張子は標準で .lisp を使います。
リスト : sample.lisp (provide :sample) (defpackage :sample (:use :cl)) (in-package :sample) (export '(greeting)) (defun greeting () (format t "hello, ASDF!~%"))
sample の中で関数 greeting を定義し、それを export しています。パッケージの説明は拙作のページ Common Lisp 入門: パッケージの基本的な使い方 をお読みください。
次に sample.asd でシステムを定義します。
リスト : sample.asd (defsystem :sample :description "ASDF Sample Program" :version "0.0.1" :author "Makoto Hiroi" :license "MIT" :depends-on () :components ((:file "sample")))
システムは defsystem で定義します。defsystem の後にシステム名を書き、そのあとにシステムの情報 (:description, :version, :author, :license など)、システムの依存関係 (:depends-on)、ソースファイル (:components ((:file "..."))) などを記述します。sample.lisp の場合、依存するシステムは無いので、ソースファイルを記述するだけで ASDF は動作しますが、システムを外部に公開するのであれば、他の情報も記述しておいたほうがいいでしょう。
実行例を示します。
$ sbcl
This is SBCL 2.1.11.debian,
・・・略・・・
* (require :sample)
; compiling file "/home/mhiroi/common-lisp/sample/sample.lisp" (written ... 略 ...):
; wrote /home/mhiroi/.cache/common-lisp/sbcl-2.1.11.debian-linux-x64/home/mhiroi/common-lisp/sample/
sample-tmpGHU3ALSV.fasl
; compilation finished in 0:00:00.010
("SAMPLE")
* (use-package :sample)
T
* (greeting)
hello, ASDF!
NIL
(require :sample) を評価すると、sample.lisp がコンパイルされて、fasl ファイルが ~/.cache/common-lisp/ 以下のサブディレクトリにキャッシュされます。ASDF はキャッシュに fasl ファイルがあればそれをロードします。
$ sbcl
This is SBCL 2.1.11.debian,
・・・略・・・
* (require :sample)
("SAMPLE")
fasl ファイルがシステムのソースファイルより古い場合、ASDF は自動的に再コンパイルしてキャッシュを更新します。
Lisp / Scheme の場合、作成した関数は REPL で簡単にテストすることができますが、いちいち手動で入力するよりも、テストを自動で実行できると便利です。Common Lisp にはテストフレームワークがいくつもあり、その中では FiveAM や prove が有名なようです。
拙作のページ Common Lisp 入門では、関数の使用例として REPL での実行結果を示しています。今回はライブラリの使用例として、同じことを自動化できれば十分なので、簡単なテストツールを作ってみることにしました。以下にテストツールの仕様を示します。
簡単な実行例を示します。
* (require :mintst)
("MINTST")
* (use-package :mintst)
T
* (initial)
----- test start -----
0
* (run (+ 1 2) 3)
(+ 1 2)
=> 3 OK
NIL
* (run (+ 1 2) 4)
(+ 1 2)
=> 3 NG, 4
NIL
* (run (/ 1 (+)) nil)
(/ 1 (+))
=> ERROR: arithmetic error DIVISION-BY-ZERO signalled
Operation was (/ 1 0).
NIL
* (final)
----- test end -----
TEST: 3
OK: 1
NG: 1
ERR: 1
NIL
実際にはテストプログラムをファイルに書いて、それを ASDF の関数 asdf:test-system で実行します。次のリストを見てください。
;;; ;;; mintst_tst.lisp : mintst のテスト ;;; ;;; Copyright (c) 2023 Makoto Hiroi ;;; ;;; Released under the MIT license ;;; https://opensource.org/license/mit/ ;;; (provide :mintst_tst) (defpackage :mintst_tst (:use :cl :mintst)) (in-package :mintst_tst) (export '(test)) (defun test () (initial) (run (+ 1 2) 3) (run (+ 1 2) 4) (run (/ 1 (+)) 0) (final))
リスト : mintst_tst.asd
(defsystem :mintst_tst
:description "test for mintst"
:version "0.1.0"
:author "Makoto Hiroi"
:license "MIT"
:depends-on (:mintst)
:components ((:file "mintst_tst"))
:perform (test-op (o s)
(symbol-call :mintst_tst :test)))
テストプログラムはパッケージとして定義して、その asd ファイルも作成します。名前は :mintst_tst としました。:mintst_tst は :mintst を使っているので、defpackage の :use に :mintst を追加します。そして、asd ファイルの depends-on に :mintst を追加します。これで前もって ASDF が :mintst を読み込んでくれます。mintst_tst.lisp で :mintst を require する必要はありません。
次に :perform でアクション test-op を定義します。asdf:test-system を評価するとき、test-op に定義されている処理が実行されます。デフォルトでは何も設定されていません。symbol-call は ASDF (正確には ASDF が使用しているパッケージ UIOP) の関数で、パッケージに定義されている関数を呼び出します。第 1 引数にパッケージ、第 2 引数に関数を指定します。パッケージがロードされていなければ、それも行います。これで :mintst_tst の関数 test が呼び出されます。
最後に mintst.asd を修正します。
リスト : mintst.asd (defsystem :mintst :description "minimum test tool" :version "0.1.0" :author "Makoto Hiroi" :license "MIT" :depends-on () :in-order-to ((test-op (test-op :mintst_tst))) :components ((:file "mintst")))
:in-order-to はアクションの依存関係を定義します。上記の定義では、:mintst の test-op を実行するとき、:minitst_tst の test-op が実行されます。:mintst の test-op は何もしないので、(asdf:test-system :mintst) を評価すると、:mintst_tst の関数 foo が評価されてテストが実行されます。
それでは実際に試してみましょう。
* (asdf:test-system :mintst) ; compiling file ... 略 ... ----- test start ----- (+ 1 2) => 3 OK (+ 1 2) => 3 NG, 4 (/ 1 (+)) => ERROR: arithmetic error DIVISION-BY-ZERO signalled Operation was (/ 1 0). ----- test end ----- TEST: 3 OK: 1 NG: 1 ERR: 1 T
正常に動作していますね。小さな自作ライブラリであれば、これだけでも十分役に立ちます。ASDF の詳しい説明は公式マニュアルや 参考 URL をお読みくださいませ。
;;;
;;; mintst.lisp : 簡単なテストツール
;;;
;;; Copyright (c) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :mintst)
(defpackage :mintst (:use :cl))
(in-package :mintst)
(export '(initial
run
final))
;;; 結果
(defvar *test-count* 0)
(defvar *ok-count* 0)
(defvar *ng-count* 0)
(defvar *err-count* 0)
;;; 初期化
(defun initial ()
(format t "----- test start -----~%~%")
(setf *test-count* 0
*ok-count* 0
*ng-count* 0
*err-count* 0))
;;; result と collect が等しいかチェックする
(defun check (test expr collect)
(when (null test) (setf test #'equal))
(incf *test-count*)
(format t "~a~%=> " expr)
(handler-case
(let ((result (eval expr)))
(cond
((funcall test result collect)
(incf *ok-count*)
(format t "~a OK~%~%" result))
(t
(incf *ng-count*)
(format t "~a NG, ~a~%~%" result collect))))
(error (c)
(incf *err-count*)
(format t "ERROR: ~a~%~%" c))))
;;; テストの実行
(defmacro run (expr collect &key (test nil))
`(mintst::check ,test ',expr ,collect))
;;; 終了
(defun final ()
(format t
"----- test end -----~%TEST: ~d~%OK: ~d~%NG: ~d~%ERR: ~d~%"
*test-count* *ok-count* *ng-count* *err-count*))
『お気楽 Common Lisp プログラミング入門 : 自作ライブラリ編』で作成したライブラリは『MIT ライセンス』で配布します。どうぞご自由にお使いくださいませ。