[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
この章では、GaucheのモジュールのセマンティクスとAPIを述べます。 Gaucheで使われているモジュールの書法についてはGaucheのモジュールを書くも 併せて参照して下さい。
4.11.1 モジュールのセマンティクス | ||
4.11.2 モジュールとライブラリ | ||
4.11.3 モジュールの定義と選択 | ||
4.11.4 モジュールの使用 | ||
4.11.5 モジュールの継承 | ||
4.11.6 モジュールイントロスペクション | ||
4.11.7 組み込みモジュール |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
モジュールは、シンボルを束縛へとマップするオブジェクトで、 グローバル変数の解決に影響を与えます。
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
の束縛が
見えることになります。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
モジュールは実行時データ構造です。実行時に任意の名前のモジュールを 手続き的に作成することができます。
しかしほとんどのライブラリは、固有の名前空間を生成するために モジュールを用います。これにより、どの束縛をライブラリ使用者に 見せるかを制御できます。
通常ライブラリは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つの対応づけ手続き(詳細に関しては、モジュールイントロスペクション参照)
を用意しています。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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
|
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
カレントモジュールの中で、シンボルsymbolに対応する束縛をexportします。 exportされた束縛は、カレントモジュールをimportしたモジュール中で見えるようになります。
カレントモジュール中の全ての束縛をexportします。
module-nameという名前のモジュールがexportしている束縛を カレントモジュール中で使えるようにします。該当するモジュールは コンパイル時に存在していなくてはなりません。
モジュールのimportは遷移的でないことに注意してください。
つまりmodule-name
で指定されたモジュールがその内部でimport
しているモジュールは自動的にカレントモジュールにはimportされてません。
モジュールの独立性を保つための設計です。この性質により、
ライブラリモジュールの作者はいくら他のモジュールを
importしようとも利用者の名前空間を不意に汚染してしまう心配はありません。
(利用者からはそのモジュールでexportしている名前しか見えないからです。)
モジュールのインポートと必要に応じてファイルのロードを合わせて行う、
便利なマクロです。基本的に、(use foo)
は以下のふたつのフォームと
等価です。
(require "foo") (import foo) |
すなわち、まず名前“foo
”を持つライブラリファイルが(まだロードされて
いなければ)ロードされ、その中で定義されているモジュールfoo
をカレントモジュールに
インポートします。
ファイルのロードとモジュールとは直交する概念ですが、
実用的にはモジュール毎にファイルを分割するのが便利です。
必ずしもそうする必要は無く、require
と import
を別々に
使っても構いません。が、Gaucheに附属してくるライブラリはすべて、
use
マクロで使えるように書かれています。
もしモジュールが一つのファイルに収めるには大きすぎる場合、一つのメインファイルと いくつかのサブファイルに分けることも出来ます。メインファイルの中でモジュールを 定義し、サブファイルをまとめてロードするか、オートロードを設定します。
実際は、与えられたモジュール名からファイルのパス名を得るのに
手続きmodule-name->path
が使われます。デフォルトの変換規則は、
モジュール名name中のピリオド`.
'を`/
'に置換
するというものです。例えば(use foo.bar.baz)
は
(require "foo/bar/baz") (import foo.bar.baz) |
となります。これはあまりScheme風ではありませんが、便利ではあります。 将来、このマッピングルールをカスタマイズする機構が導入されるかもしれません。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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された束縛を利用することができるようになります。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
この節では、実行時にモジュールを操作する手続きをリストします。 これらの手続きにより、例えばモジュールの内部を調べたり、手続き的に 新しいモジュールを作成したり、特定のモジュールやライブラリの存在を 調べたりすることができます。ただし、モジュールは第一にコンパイル時の 構造であることを忘れないでください。実行時にモジュールをいじくるのは、 十分にモジュールの構造を理解した上で行ってください。
モジュールクラスです。
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
はモジュール名を必要とするからです)。
無名のモジュールは、一時的に隔離された名前空間を動的に作りたい時に
便利です。例えばネットワークで接続されたプログラムから送られた式を
その中で評価して、コネクションが終了したら名前空間ごと捨ててしまうという
ような場合です。無名のモジュールはシステムの内部辞書に登録されないので、
モジュールへの参照が無くなればガベージコレクトされます。
現在存在する全ての名前付きモジュールのリストを返します。 無名のモジュールは含まれません。
モジュールオブジェクトのアクセスメソッドです。 moduleの名前(シンボル)、moduleがインポートしているモジュールのリスト、 エクスポートしているシンボルのリスト、そして シンボルから束縛へのマップを行うハッシュテーブルを返します。
もしmoduleが全てのシンボルをエクスポートしている場合は、module-exports
は#t
を返します。
モジュールオブジェクト以外が渡された場合はエラーになります。
モジュールの継承に関する情報を返します。
module-parents
はmoduleが直接継承しているモジュールのリストを
返します。module-precedence-list
はmoduleのmodule precedence
list (モジュールの継承参照) を返します。
symbolのグローバルなバインディングがmoduleから 可視であれば、真を返します。moduleはモジュールオブジェクトか 既存のモジュール名を示すシンボルでなければなりません。
註: 以前、この手続きの機能はsymbol-bound?
という手続きで
実現されていました。symbol-bound?
は非推奨となり、新しいコードは
global-variable-bound?
を使わねばなりません。
この変更の理由は、symbol-bound?
がカレントモジュールをデフォルトと
しており、またその名前からも、グローバルな束縛値があたかも
(CommonLispのように)シンボルそのものの属性であるかのような誤解を招いて
いたからです。そのせいで、特にコンパイル時と実行時でカレントモジュールが
異なるような場合に多くの混乱が生じていました。
新しい名前とAPIは、グローバルな束縛値についてモジュールに問い合わせている
ということを明確にしています。
モジュールmoduleから可視の、シンボルsymbolのグローバルな 束縛値を返します。moduleはモジュールオブジェクトか 既存のモジュール名を示すシンボルでなければなりません。 symbolに対する可視のグローバル束縛が無い場合は、 default引数があたえられていればその値を返し、 無ければエラーを通知します。
モジュール名symbolを、パス名の一部(require
やprovide
が
使うような)へと変換します。
module-name->path
の逆関数です。
特定のライブラリやモジュールがシステムにインストールされて使える状態にあるか 調べたりする場合は、ライブラリの操作を参照して下さい。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Gauche起動時にいくつかのモジュールがあらかじめ定義されています。
このモジュールはR5RSで述べられている"null environment"に相当します。 R5RSの構文要素への束縛だけを含んだモジュールです。
このモジュールはnull
モジュール内の束縛全てに加えて、
R5RSで定義されている全ての手続きの束縛を含みます。
select-module
によって一度null
やscheme
モジュールに
入ると、そこから他のモジュールに移ることはできなくなることに注意してください。
これらのモジュールからは、あらゆるモジュール操作構文が不可視だからです。
このモジュールはscheme
モジュール内の全ての束縛に加え、
Gaucheの組込み手続きや構文が含まれています。
このモジュールはユーザコードがコンパイルされる既定のモジュールです。
gauche
モジュール内の全ての束縛がインポートされています。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] |
This document was generated by Shiro Kawai on November, 22 2009 using texi2html 1.78.