この章では、GaucheのモジュールのセマンティクスとAPIを述べます。 Gaucheで使われているモジュールの書法についてはGaucheのモジュールを書くも 併せて参照して下さい。
R7RSプログラムでは、モジュールに相当するものは「ライブラリ」と呼ばれ、 Gaucheとは異なる構文で定義します。詳しくはR7RSライブラリ形式を参照してください。
• モジュールのセマンティクス: | ||
• モジュールとライブラリ: | ||
• モジュールの定義と選択: | ||
• モジュールの使用: | ||
• モジュールの継承: | ||
• モジュールイントロスペクション: | ||
• 組み込みモジュール: |
モジュールは、シンボルを束縛へとマップするオブジェクトで、 グローバル変数の解決に影響を与えます。
CommonLispのパッケージは名前からシンボルへのマッピングを行いますが、
Gaucheでは同じ名前を持つシンボルは原則としてeq?
です
(例外はインターンされていないシンボルです。シンボル参照)。
しかし、Gaucheのシンボルは「値」のスロットを持っていません。
モジュールによってシンボルに対応する束縛が見付けられ、値はそこに
格納されています。
モジュールが違えば同じシンボルは別々の束縛へとマップされ、違う値を
返します。
;; 二つのモジュールAとBを作成し、グローバル変数'x'をその中で定義 (define-module A (define x 3)) (define-module B (define x 4)) ;; #<symbol 'x'> ---[module A]--> #<binding that has 3> (with-module A x) ⇒ 3 ;; #<symbol 'x'> ---[module B]--> #<binding that has 4> (with-module B x) ⇒ 4
モジュールは、自身が持つ一部または全ての束縛を他のモジュールからも 使えるようにexportすることができます。あるモジュールXが他の モジュールYをimportすると、 モジュールYでexportされている束縛が元のモジュールXから見えるようになります。 モジュールはいくつでも他のモジュールをimportすることができます。
(define-module A (export pi) (define pi 3.1416)) (define-module B (export e) (define e 2.71828)) (define-module C (import A B)) (select-module C) (* pi e) ⇒ 8.539748448
また、モジュールは継承することもできます。
既存のモジュールを継承したモジュールに新しい束縛を足してexportすることにより、
既存のモジュールを拡張することができます。新しいモジュールの内部からは、
継承元のモジュールの束縛が(exportされていないものも含め)全て見えます。
(新しく作られるモジュールはデフォルトでgauche
モジュールを継承しています。
新しいモジュールからgauche
の組込み手続き等が使えるのはそのためです)。
外からは、新しいモジュールには元のモジュールの全てのexportされた束縛と
新たに追加されexportされた束縛が見えます。
;; Module A defines and exports deg->rad. ;; A binding of pi is not exported. (define-module A (export deg->rad) (define pi 3.1416) ;; not exported (define (deg->rad deg) (* deg (/ pi 180)))) ;; Module Aprime defines and exports rad->deg. ;; The binding of pi is visible from inside Aprime. (define-module Aprime (extend A) (export rad->deg) (define (rad->deg rad) (* rad (/ 180 pi)))) ;; Module C imports Aprime. (define-module C (import Aprime) ;; Here, both deg->rad and rad->deg are visible, ;; but pi is not visible. )
コンパイル中のどの時点でも、「カレントモジュール」が一意に決定され、 グローバル変数の束縛はそのカレントモジュールを起点に探されます。 その変数の束縛が見付かれば、変数参照の式はその束縛へアクセスするコードとして コンパイルされます。もしコンパイラが束縛を見付けられなかった場合、 変数参照の式はカレントモジュールでマークされ、束縛の解決はランタイムへと 先送りされます。すなわち、ランタイムにその変数が使われる時点で 再びマークされていたモジュールから束縛の探索が行われます (ランタイムでのカレントモジュールからでは無いことに注意)。 束縛が見付かれば、束縛へアクセスするコードがコンパイルされたコード列に 挿入されます。見付からなければ’undefined variable’エラーが報告されます。
グローバル変数に対して適切な束縛がひとたび発見されれば、 その束縛へのアクセスはコンパイルされたコードに埋め込まれ、 その変数の束縛の探索は二度と行われません。
define
やdefine-syntax
等の定義を行う特殊形式は
カレントモジュールに束縛を挿入します。これは、importしたり継承したりしている
モジュールの同名の束縛をシャドウします。
グローバル変数の束縛の解決は次の手順で行われます。 まずカレントモジュールが探されます。次に、importしているモジュールが importされた逆の順番に並べられ、それぞれについてその モジュールおよびそのモジュールの先祖(継承されているモジュール)が順に探されます。 importは遷移的ではありません;importされたモジュールがimportしているモジュール… というふうに再帰的に辿ることはしません。 最後に、カレントモジュールの先祖が順に探されます。
この順序は、複数のモジュールで同じ名前が定義され、あなたのモジュールが
その両方をインポートしている場合に重要になります。
その名前があなたのモジュールで
定義されていないとして、もしモジュールA
がまずimportされ、
次にB
がimportされている場合、あなたのコードはB
の
束縛を見ることになります。
A
をimportしてB
をimportした後に再びA
をimport
した場合、後のimportの方が効力を持ちます。すなわち、A
の束縛が
見えることになります。
もし、二つのモジュールが同名の束縛をエクスポートしており、 その両方にアクセスしたければ、一方もしくは両方の名前にプレフィクスを つけることができます。詳しくはモジュールの使用を参照してください。
モジュールは実行時データ構造です。実行時に任意の名前のモジュールを 手続き的に作成することができます。
しかしほとんどのライブラリは、固有の名前空間を生成するために モジュールを用います。これにより、どの束縛をライブラリ使用者に 見せるかを制御できます。(ここでの「ライブラリ」は、R7RSのライブラリだけでなく より広い意味で使っています。)
通常ライブラリは1つ以上のSchemeソースファイル形式で提供されます。
したがって、ファイル名をモジュール名に対応づける(またはその逆の)
規約にしておけば便利です。そうすれば、たとえば、ライブラリーファイルを
ロードしたり、use
マクロを使ってモジュールを一動作で、
インポートしたりできます。
当分の間、Gauche はこの対応づけのための単純なルールを使用します。すなわち、
モジュール名は、例えば gauche.mop.validator
のように ‘.
’
(ピリオド)記号で階層的に区切って構成されます。このようなモジュールが
要求されても、現在の実行時環境に存在しない場合には、Gauche は
ピリオド記号をディレクトリ区切りに変換して gauche/mop/validator
のようにモジュール名からパス名に変換します。その後、
gauche/mop/validator.scm
をロードパスから探します。
これが単にデフォルトの振る舞いであることに注意してください。
理論上、1つのSchemeソース・ファイルは多数のモジュールを含むことがあります。
あるいは、1つのモジュール実装は多数のファイルにまたがることもありえます。
将来、特別なケースのために、この対応付けをカスタマイズするフックを
用意するかもしれません。したがって、モジュールおよびライブラリーファイルを
扱うルーチンを書く場合には、上記のデフォルトルールを盲目的に適用しないで
ください。Gaucheは module-name->path
と path->module-name
という
2つの対応づけ手続き(詳細に関しては、モジュールイントロスペクション参照)
を用意しています。
nameはシンボルでなければなりません。 名前nameを持つモジュールが存在しなければまず作成します。 それから、body … をモジュールname中で評価します。
名前nameを持つモジュールをカレントモジュールとします。 その名前を持つモジュールが無ければエラーとなります。
select-module
がSchemeファイルの中で用いられた場合、
その効果はそのファイルの終了までに限られます。select-module
を中で呼んでいる
ファイルをloadやrequireしても、呼んだ側のカレントモジュールは影響を受けません。
名前nameを持つモジュールをカレントモジュールとした状態でbody … を順に評価し、最後の結果を返します。該当するモジュールが存在しなければエラーとなります。
コンパイル時点でのカレントモジュールに評価されます。 これは手続きではなく特殊形式です。 Gaucheではモジュールはコンパイル時に静的に決定されます。
(define-module foo
(export get-current-module)
(define (get-current-module) (module-name (current-module))))
(define-module bar
(import foo)
(get-current-module)) ⇒ foo ; not bar
[R7RS base] カレントモジュールから、specで指定される束縛をexportします。 exportされた束縛は、カレントモジュールをimportしたモジュール中で見えるようになります。
各specは次のどちらかの形式でなければなりません。nameとexport-name はシンボルです。
name
名前nameを持つ束縛がexportされます。
(rename name exported-name)
名前nameを持つ束縛が、exported-nameという別名でexportされます。
註: Gaucheのexport
は単なるスペシャルフォームで
プログラムの途中に書くこともできますが、
R7RSのexport
はライブラリ宣言の一部で
define-library
フォームの直下にしか書けません。
詳しくはR7RSライブラリ形式を参照してください。
カレントモジュール中の全ての束縛をexportします。
import-specで指定されるモジュールがexportしている 束縛のすべてもしくはいくつかを、カレントモジュール中で使えるようにします。 import-specは以下の形式です。
<import-spec> : <module-name> | (<module-name> <import-option> ...) <import-option> : :only (<symbol> ...) | :except (<symbol> ...) | :rename ((<symbol> <symbol>) ...) | :prefix <symbol> <module-name> : <symbol>
module-nameで指定される名前のモジュールは このフォームがコンパイルされる時点までに存在していなければなりません。
モジュールのimportは遷移的ではありません。
つまりmodule-name
で指定されたモジュールがその内部でimport
しているモジュールは自動的にカレントモジュールにはimportされてません。
モジュールの独立性を保つための設計です。この性質により、
ライブラリモジュールの作者はいくら他のモジュールを
importしようとも利用者の名前空間を不意に汚染してしまう心配はありません。
(利用者からはそのモジュールでexportしている名前しか見えないからです。)
import-optionは束縛がどのようにインポートされるかを制御します。
:only
がある場合、<symbol> …
に挙げられた名前を
持つ束縛のみがインポートされます。:except
がある場合は逆に、
エクスポートされている束縛のうち挙げられた名前を持つもの以外が
インポートされます。:rename
は各2要素のリストの最初の名前
を持つ束縛が2番目の名前へとリネームされます。
:prefix
があると、元の名前の前に
指定されるシンボルが付加された名前で束縛が見えるようになります。
import-optionが指定されなければ、module-nameのすべての
エクスポートされた束縛がプレフィクス無しでインポートされます。
(define-module M (export x y) (define x 1) (define y 2) (define z 3)) (import M) x ⇒ 1 z ⇒ エラー。 zはMからエクスポートされなていない (import (M :only (y))) x ⇒ エラー。xは:onlyリストに含まれない。 (import (M :except (y))) y ⇒ エラー。yは:exceptにより除外されている。 (import (M :prefix M:)) x ⇒ error M:x ⇒ 1 M:y ⇒ 2
一つ以上のインポートオプションが与えられた場合、
それは出現順に処理されます。すなわち、:prefix
が
最初に現れた場合、その後に来る:only
や:except
はプレフィックスつきの名前を使って指定しなければなりません。
註: R7RSにもimport
フォームがありますが、若干構文と意味が異なります。
3つのimport形式を参照してください。
モジュールのインポートと必要に応じてファイルのロードを合わせて行う、
便利なマクロです。基本的に、(use foo)
は以下のふたつのフォームと
等価です。
(require "foo") (import foo)
すなわち、まず名前“foo
”を持つライブラリファイルが(まだロードされて
いなければ)ロードされ、その中で定義されているモジュールfoo
をカレントモジュールに
インポートします。
キーワード引数only, except, prefixは
import
にインポートオプションとして渡されます。
(use srfi.1 :only (iota) :prefix srfi-1:) (srfi-1:iota 3) ⇒ (0 1 2)
ファイルのロードとモジュールとは直交する概念ですが、
実用的にはモジュール毎にファイルを分割するのが便利です。
必ずしもそうする必要は無く、require
と import
を別々に
使っても構いません。が、Gaucheに附属してくるライブラリはすべて、
use
マクロで使えるように書かれています。
もしモジュールが一つのファイルに収めるには大きすぎる場合、一つのメインファイルと いくつかのサブファイルに分けることも出来ます。メインファイルの中でモジュールを 定義し、サブファイルをまとめてロードするか、オートロードを設定します。
実際は、与えられたモジュール名からファイルのパス名を得るのに
手続きmodule-name->path
が使われます。デフォルトの変換規則は、
モジュール名name中のピリオド‘.
’を‘/
’に置換
するというものです。例えば(use foo.bar.baz)
は
(require "foo/bar/baz") (import foo.bar.baz)
となります。これはあまりScheme風ではありませんが、便利ではあります。 将来、このマッピングルールをカスタマイズする機構が導入されるかもしれません。
use
されるファイルがトップレベル定義を持つ場合、ファイル内でモジュールが
明示されてることが必要です (通常はdefine-module
/select-module
やdefine-library
が使われます)。
もし、“Attempted to create a binding in a sealed module:
module: #<module gauche.require-base>”というエラーが出たら、
use
したファイルがモジュール指定を持っていないということです。
詳しくはrequireとprovideを参照してください。
export-importメカニズムは、次のような場合をうまく処理できません。
このような場合にモジュールの継承が使えます。
カレントモジュールが、module-name …に挙げられたモジュールを 継承するようにします。それまでの継承の情報は捨てられ、module-name … から計算される継承情報が有効になります。
新たに作られるモジュールはデフォルトでgauche
モジュールを継承しています。
例えばそのモジュールに(extend scheme)
というフォームを入れた場合、
その時点でそのモジュールはscheme
モジュール(R5RSで定義された束縛
のみを含む)を直接継承するようになります。したがって、そのフォームの後で
’import’ やその他gauche
特有の束縛はそのモジュール内では
使えなくなります。
module-nameに挙げられたモジュールがまだ存在しなかった場合、
extend
はuse
と同じメカニズムを使ってファイルをロードすることを
試みます。
モジュールは複数のモジュールを継承することができます。 丁度、クラスが複数のクラスを継承できるのと同じようにです。 多重継承の場合、次のようにしてモジュール間の優先順位が決められます。
各モジュールはmodule precedence listというモジュールのリストを
持っています。そこにリストされた順に束縛が探されます。
モジュールが複数のモジュールを多重継承した場合、継承される各モジュールの
module precedence listを、次に挙げる制約を満たすようにマージ
したものが新たなmodule precedence listとなります:
(1) あるmodule precedence listでモジュールAがモジュールBより前に現れていたら、
結果のmodule precedence listでもAはBより前に現れる:
(2) モジュールAがモジュールBよりextend
フォームで前に現れていたら、
結果のmodule precedence listでもAはBより前に現れる。
この条件を満たすようなmodule precedence listが構成できない場合はエラーとなります。
例えばあなたがライブラリを3つのモジュール、
mylib.base
、mylib.util
、mylib.system
に分けて
書いたとしましょう。次のように書けば、これらのモジュールを
一つのmylib
モジュールに見せることができます。
(define-module mylib (extend mylib.system mylib.util mylib.base))
このライブラリモジュールのユーザは (use mylib)
とするだけで
全てのサブモジュールのexportされた束縛を利用することができるようになります。
この節では、実行時にモジュールを操作する手続きをリストします。 これらの手続きにより、例えばモジュールの内部を調べたり、手続き的に 新しいモジュールを作成したり、特定のモジュールやライブラリの存在を 調べたりすることができます。ただし、モジュールは第一にコンパイル時の 構造であることを忘れないでください。実行時にモジュールをいじくるのは、 十分にモジュールの構造を理解した上で行ってください。
モジュールクラスです。
objがモジュールなら真の値を返します。
名前がシンボルnameであるようなモジュールを返します。
その名前をもつモジュールが存在しなければ、#f
を返します。
シンボルの名前nameを持つモジュールを作成して返します。
その名前を持つモジュールが既に存在していた場合、その動作は
if-existsキーワード引数で指定されます。
if-exists引数が:error
である場合(デフォルト)、
エラーが報告されます。それが#f
である場合は単に#f
が返されます。
モジュールを実行時に動的に生成することは、通常のスクリプトでは
あまり必要とはされません。既に書かれたプログラムの解釈においては、
モジュールは名前で指定されている必要があるからです。
構文define-module
、import
、extend
、with-module
等はモジュールそのものではなくモジュール名を取ります。
これは、モジュールが本質的にコンパイル時の構造であるためです。
しかし、動的に作られるモジュールが有用な場合もあります—プログラムそのものが、
動的に作られる場合です。eval
にモジュールを渡して、
そのような動的に作られたプログラムがそのモジュールの中で
コンパイルされ評価されるようにできます。
また、nameに#f
を渡すことで無名のモジュールを作ることもできます。
無名のモジュールはfind-module
で探すことはできませんし、
他のモジュールからimport
することもextend
されることも
できません(import
やextend
はモジュール名を必要とするからです)。
無名のモジュールは、一時的に隔離された名前空間を動的に作りたい時に
便利です。例えばネットワークで接続されたプログラムから送られた式を
その中で評価して、コネクションが終了したら名前空間ごと捨ててしまうという
ような場合です。無名のモジュールはシステムの内部辞書に登録されないので、
モジュールへの参照が無くなればガベージコレクトされます。
R7RSでは、environment
手続きによって一時的なモジュールを
作ることもできます。scheme.eval
- R7RS evalを参照してください。
現在存在する全ての名前付きモジュールのリストを返します。 無名のモジュールは含まれません。
モジュールオブジェクトのアクセスメソッドです。 moduleの名前(シンボル)、moduleがインポートしているモジュールのリスト、 エクスポートしているシンボルのリスト、そして シンボルから束縛へのマップを行うハッシュテーブルを返します。
モジュールオブジェクト以外が渡された場合はエラーになります。
特定のシンボルがモジュールからエキスポートされているかどうかを調べるには、
下に説明するmodule-exports?
を使ってください。
module-exports
で全エキスポートシンボルのリストを取ってそこから探すより
ずっと速いです。
モジュールの継承に関する情報を返します。
module-parents
はmoduleが直接継承しているモジュールのリストを
返します。module-precedence-list
はmoduleのmodule precedence
list (モジュールの継承参照) を返します。
symbolのグローバルな束縛がmodule内部から
可視であれば#t
を、そうでなければ#f
を返します。
つまり、(module-binds? 'M 'v)
が#t
を返したら、
v
はグローバルに束縛された識別子として
(変数もしくは構文キーワードとして)
モジュールM
の中で使えるということです。
moduleはモジュールオブジェクトか
既存のモジュール名を示すシンボルでなければなりません。
この手続きはmodule内部からの見え方を扱うので、
名前symbolがmoduleからエクスポートされているか、
あるいはエクスポート時にリネームされているかには影響されません。
module外部からの視点で束縛の可視性を調べたい場合は、
下のmodule-exports?
を使ってください。
註: Lispは伝統的に、グローバル変数の束縛を調べる手続きをsymbol-bound?
と呼んできました。しかし、Gaucheでは束縛はモジュールにより管理されるのに対し、
その名前はシンボルに焦点を当ててしまっています。
以前のGaucheではglobal-variable-bound?
という名前を使っていて、
それは互換性のために今でも残されていますが、この操作はモジュールに関するものであること、
また、変数だけでなく構文キーワードを含めた束縛に関するものであることを明確にするために、
現在の名前になりました。
symbolの束縛がmoduleの外部から可視であれば#t
を、
そうでなければ#f
を返します。
すなわち、(module-exports? 'M 'v)
が#t
を返したなら、
M
をインポートすればv
がグローバル変数もしくは構文キーワードとして
使えるということです。
moduleはモジュールオブジェクトか
既存のモジュール名を示すシンボルでなければなりません。
module-binds?
と違い、この手続きは束縛が外からどう見えるかを検査します。
次のようなモジュールがあるとしましょう。
(define-module M (export a (rename b bee)) (define a 1) (define b 2) (define c 3))
module-binds?
はM
の中で束縛がどう見えるかを調べるので、
a
、b
、c
については#t
を返しますが、
bee
については#f
を返します。
(module-binds? 'M 'a) ⇒ #t (module-binds? 'M 'b) ⇒ #t (module-binds? 'M 'c) ⇒ #t (module-binds? 'M 'bee) ⇒ #f ; renamed export doesn't affect in M
module-exports?
はモジュールを外から見た場合の束縛を調べるので、
a
とbee
について#t
を返しますが、b
とc
には
#f
を返します。
(module-exports? 'M 'a) ⇒ #t (module-exports? 'M 'b) ⇒ #f ; because of renamed export (module-exports? 'M 'c) ⇒ #f ; not exported (module-exports? 'M 'bee) ⇒ #t ; external name
モジュールmoduleから可視の、シンボルsymbolのグローバルな 束縛値を返します。moduleはモジュールオブジェクトか 既存のモジュール名を示すシンボルでなければなりません。 symbolに対するグローバルな束縛がmoduleから見えない場合は、 default引数があたえられていればその値を返し、 無ければエラーを通知します。
これは以前はglobal-variable-ref
と呼ばれていましたが、
モジュールの操作であること、また束縛は変数とは限らず構文キーワードでも良いことを
明確にするために名前を変えました。古い名前も使えますが非推奨です。
Deprecated.
それぞれ、module-binds?
とmodule-binding-ref
の古い名前です。
新規コードでは新しい名前を使ってください。
モジュール名symbolを、パス名の一部(require
やprovide
が
使うような)へと変換します。
module-name->path
の逆関数です。
特定のライブラリやモジュールがシステムにインストールされて使える状態にあるか 調べたりする場合は、ライブラリの操作を参照して下さい。
Gauche起動時にいくつかのモジュールがあらかじめ定義されています。
このモジュールはR5RSで述べられている"null environment"に相当します。 R5RSの構文要素への束縛だけを含んだモジュールです。
このモジュールはnull
モジュール内の束縛全てに加えて、
R5RSで定義されている全ての手続きの束縛を含みます。
select-module
によって一度null
やscheme
モジュールに
入ると、そこから他のモジュールに移ることはできなくなることに注意してください。
これらのモジュールからは、あらゆるモジュール操作構文が不可視だからです。
このモジュールはscheme
モジュール内の全ての束縛に加え、
Gaucheの組込み手続きや構文が含まれています。
このモジュールはユーザコードがコンパイルされる既定のモジュールです。
gauche
モジュール内の全ての束縛がインポートされています。
GaucheがデフォルトのGAUCHE_KEYWORD_IS_SYMBOL
モードで実行されている時は、
キーワード (:
で始まるシンボル) が自動的にこれらのモジュールの中で
自分自身に束縛されます。(詳しくはキーワードを参照してください。)
keyword
モジュールはキーワードの自己束縛をエクスポートせず、
gauche.keyword
モジュールはエクスポートします。前者は主に
内部的に使うためのもので、プログラマが知る必要があるのは後者です。
デフォルトのモジュール継承を使っている場合、キーワードモジュールは
継承チェインの中に含まれているので、このモジュールを陽に使用する必要はありません。
gauche
を継承しないモジュールを書いていて、キーワードをクオートせずに
使いたい場合にこのモジュールをインポートしてください。
例えばR7RSプログラムやライブラリは、(import (gauche keyword))
もしくは(import (gauche base))
(後者はgauche.keyword
を
継承しています)しない限り、キーワードにクオートが必要です。
次のR7RSプログラムはgauche.base
をimportすることで、
Gaucheの組み込みの識別子とともに自己束縛されたキーワードが使えるようになります。
;; R7RS program (import (scheme base) (gauche base)) ; import gauche builtins and keywords ;; You can use :directory without quote, for it is bound to itself. (sys-exec "ls" '("ls" "-l") :directory "/")
ただ、もうちょっと凝ったインポートをする場合、
デフォルトではキーワードも
単なるインポートされるシンボルにすぎないということを覚えておいてください。
例えば次のコードではGaucheの組み込み識別子をgauche/
というプレフィクス
つきでインポートしています。この場合、gauche.base
の継承経由で
インポートされているキーワードもプレフィクスがつけられることになります。
キーワードをいちいちプレフィクスつき、もしくはクオートつきで書きたくなければ、
gauche.keyword
を別にインポートしましょう。
;; R7RS program (import (scheme base) (prefix (gauche base) gauche/) ; use gauche builtin with gauche/ prefix (gauche keyword)) ; imports keywords ;; Without importing gauche.keyword, ;; you need to write ':directory (gauche/sys-exec "ls" '("ls" "-l") :directory "/")