言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Common lisp の package と require でライブラリを使ったプログラムを書く方法。

package

Common lisp の package について調べてみました。package というのはネームスペースのような物で、プログラムの中を区切って名前が混じらないようにする仕組みです。名前の管理だけを行うので、一つのファイルに複数の package を定義する事も出来ます。これはライブラリの読み込み機能も付いた Scheme の libraryと比べて低レベルで面白いので clisp で調べてみました。

まず clisp を起動すると、あらかじめデフォルトの package の中に居る事になっています。現在の package は *package* で知る事が出来ます。システムに現在登録されている package を全部知るには list-all-packages を使います。

> *package*
#<PACKAGE COMMON-LISP-USER>

> (list-all-packages)
(#<PACKAGE GSTREAM> #<PACKAGE GRAY> #<PACKAGE I18N> #<PACKAGE SOCKET>
 #<PACKAGE SCREEN> #<PACKAGE CUSTOM> #<PACKAGE EXT> #<PACKAGE CLOS>
 #<PACKAGE CS-COMMON-LISP-USER> #<PACKAGE CS-COMMON-LISP> #<PACKAGE CHARSET>
 #<PACKAGE KEYWORD> #<PACKAGE SYSTEM> #<PACKAGE COMMON-LISP-USER>
 #<PACKAGE COMMON-LISP> #<PACKAGE EXPORTING> #<PACKAGE POSIX> #<PACKAGE REGEXP>)

新しいパッケージ propella を make-package で定義して in-package で中に入ります。

> (make-package 'propella)
#<PACKAGE PROPELLA>
> (in-package propella)
#<PACKAGE PROPELLA>
PROPELLA> *package*
#<PACKAGE PROPELLA>

変数 hello を propella package で定義する。package の中へは :: でアクセスする。

PROPELLA> (defvar hello "Hello, Propella!")
HELLO
PROPELLA> (in-package common-lisp-user)
#<PACKAGE COMMON-LISP-USER>
> propella::hello
"Hello, Propella!"

propella::hello を export して、他の package から修飾無しで使えるようにする。

> (in-package propella)
#<PACKAGE PROPELLA>
PROPELLA> (export 'hello)
T
PROPELLA> (in-package common-lisp-user)
#<PACKAGE COMMON-LISP-USER>
> (use-package 'propella)
T
> hello
"Hello, Propella!"

面白いのは、この export を実験する時に何度か同じシンボルが定義されているというエラーになりました。どうやら common-lisp-user の中で hello とうっかり打ち込むだけで common-lisp-user::hello が知らない間に定義されていたようでした。そう言うときは (unintern 'hello) でシンボルを消す事が出来ます。

require と provide

require と provide を使ってライブラリを作る仕組みを書きます。どうも ASDF という他の仕組みもあるようですが、HyperSpec の中に仕組みだけを使って作ってみました。まずライブラリはこんな感じで、mylib.lisp という名前で保存します。

;; mylib.lisp

(provide 'mylib)

(defpackage mylib
  (:use common-lisp)
  (:export hello))

(in-package mylib)

(defvar hello "Hello, My Library!")

この provide と下にある require はセットになって動きます。provide は自分が実行された事を記録しておく関数で、require はこの値をチェックして、二度ライブラリが呼ばれないようにします。また、require はプラットフォーム依存の方法でシンボルからファイルを探し当てプログラムをロードします。

defpackage は make-package の高級版で、利用する package を書く :use や、公開するシンボルを書く :export を同時に書けます。

次にメインプログラムは main.lisp という名前で保存します。

(require 'mylib)
(print mylib:hello)

実行結果です。

$ clisp main.lisp

"Hello, My Library!"

common lispscheme はプラットフォームの違いについてすごい意識しているからか、これだけの情報を集めるのに滅茶苦茶苦労しました。PythonRuby だったらマニュアルの最初の方を読めばする分かる事なのに、どういうファイル名で保存して、どういう風に実行したら良いかという基本的な事が見つかりにくいようになっているので初心者にはつらいです。