今まで 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 ライセンス』で配布します。どうぞご自由にお使いくださいませ。