For Gauche 0.9.5


Previous: , Up: 基本的な構文   [Contents][Index]

4.13 モジュール

この章では、GaucheのモジュールのセマンティクスとAPIを述べます。 Gaucheで使われているモジュールの書法についてはGaucheのモジュールを書くも 併せて参照して下さい。

R7RSプログラムでは、モジュールに相当するものは「ライブラリ」と呼ばれ、 Gaucheとは異なる構文で定義します。詳しくはR7RSライブラリ形式を参照してください。


Next: , Previous: , Up: モジュール   [Contents][Index]

4.13.1 モジュールのセマンティクス

モジュールは、シンボルを束縛へとマップするオブジェクトで、 グローバル変数の解決に影響を与えます。

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’エラーが報告されます。

グローバル変数に対して適切な束縛がひとたび発見されれば、 その束縛へのアクセスはコンパイルされたコードに埋め込まれ、 その変数の束縛の探索は二度と行われません。

definedefine-syntax等の定義を行う特殊形式は カレントモジュールに束縛を挿入します。これは、importしたり継承したりしている モジュールの同名の束縛をシャドウします。

グローバル変数の束縛の解決は次の手順で行われます。 まずカレントモジュールが探されます。次に、importしているモジュールが importされた逆の順番に並べられ、それぞれについてその モジュールおよびそのモジュールの先祖(継承されているモジュール)が順に探されます。 importは遷移的ではありません;importされたモジュールがimportしているモジュール… というふうに再帰的に辿ることはしません。 最後に、カレントモジュールの先祖が順に探されます。

この順序は、複数のモジュールで同じ名前が定義され、あなたのモジュールが その両方をインポートしている場合に重要になります。 その名前があなたのモジュールで 定義されていないとして、もしモジュールAがまずimportされ、 次にBがimportされている場合、あなたのコードはBの 束縛を見ることになります。

AをimportしてBをimportした後に再びAをimport した場合、後のimportの方が効力を持ちます。すなわち、Aの束縛が 見えることになります。

もし、二つのモジュールが同名の束縛をエクスポートしており、 その両方にアクセスしたければ、一方もしくは両方の名前にプレフィクスを つけることができます。詳しくはモジュールの使用を参照してください。


Next: , Previous: , Up: モジュール   [Contents][Index]

4.13.2 モジュールとライブラリ

モジュールは実行時データ構造です。実行時に任意の名前のモジュールを 手続き的に作成することができます。

しかしほとんどのライブラリは、固有の名前空間を生成するために モジュールを用います。これにより、どの束縛をライブラリ使用者に 見せるかを制御できます。(ここでの「ライブラリ」は、R7RSのライブラリだけでなく より広い意味で使っています。)

通常ライブラリは1つ以上のSchemeソースファイル形式で提供されます。 したがって、ファイル名をモジュール名に対応づける(またはその逆の) 規約にしておけば便利です。そうすれば、たとえば、ライブラリーファイルを ロードしたり、use マクロを使ってモジュールを一動作で、 インポートしたりできます。

当分の間、Gauche はこの対応づけのための単純なルールを使用します。すなわち、 モジュール名は、例えば gauche.mop.validator のように ‘.’ (ピリオド)記号で階層的に区切って構成されます。このようなモジュールが 要求されても、現在の実行時環境に存在しない場合には、Gauche は ピリオド記号をディレクトリ区切りに変換して gauche/mop/validator のようにモジュール名からパス名に変換します。その後、 gauche/mop/validator.scm をロードパスから探します。

これが単にデフォルトの振る舞いであることに注意してください。 理論上、1つのSchemeソース・ファイルは多数のモジュールを含むことがあります。 あるいは、1つのモジュール実装は多数のファイルにまたがることもありえます。 将来、特別なケースのために、この対応付けをカスタマイズするフックを 用意するかもしれません。したがって、モジュールおよびライブラリーファイルを 扱うルーチンを書く場合には、上記のデフォルトルールを盲目的に適用しないで ください。Gaucheは module-name->pathpath->module-name という 2つの対応づけ手続き(詳細に関しては、モジュールイントロスペクション参照) を用意しています。


Next: , Previous: , Up: モジュール   [Contents][Index]

4.13.3 モジュールの定義と選択

Special Form: define-module name body …

nameはシンボルでなければなりません。 名前nameを持つモジュールが存在しなければまず作成します。 それから、body … をモジュールname中で評価します。

Special Form: select-module name

名前nameを持つモジュールをカレントモジュールとします。 その名前を持つモジュールが無ければエラーとなります。

select-moduleがSchemeファイルの中で用いられた場合、 その効果はそのファイルの終了までに限られます。select-moduleを中で呼んでいる ファイルをloadやrequireしても、呼んだ側のカレントモジュールは影響を受けません。

Special Form: with-module name body …

名前nameを持つモジュールをカレントモジュールとした状態でbody … を順に評価し、最後の結果を返します。該当するモジュールが存在しなければエラーとなります。

Special Form: current-module

コンパイル時点でのカレントモジュールに評価されます。 これは手続きではなく特殊形式です。 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

Next: , Previous: , Up: モジュール   [Contents][Index]

4.13.4 モジュールの使用

Special Form: export spec …

[R7RS] カレントモジュールから、specで指定される束縛をexportします。 exportされた束縛は、カレントモジュールをimportしたモジュール中で見えるようになります。

specは次のどちらかの形式でなければなりません。nameexport-name はシンボルです。

name

名前nameを持つ束縛がexportされます。

(rename name exported-name)

名前nameを持つ束縛が、exported-nameという別名でexportされます。

註: Gaucheのexportは単なるスペシャルフォームで プログラムの途中に書くこともできますが、 R7RSのexportはライブラリ宣言の一部で define-libraryフォームの直下にしか書けません。 詳しくはR7RSライブラリ形式を参照してください。

Special Form: export-all

カレントモジュール中の全ての束縛をexportします。

Special Form: import import-spec …

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形式を参照してください。

Macro: use name :key only except rename prefix

モジュールのインポートと必要に応じてファイルのロードを合わせて行う、 便利なマクロです。基本的に、(use foo) は以下のふたつのフォームと 等価です。

(require "foo")
(import foo)

すなわち、まず名前“foo”を持つライブラリファイルが(まだロードされて いなければ)ロードされ、その中で定義されているモジュールfooをカレントモジュールに インポートします。

キーワード引数only, except, prefiximportにインポートオプションとして渡されます。

(use srfi-1 :only (iota) :prefix srfi-1:)

(srfi-1:iota 3) ⇒ (0 1 2)

ファイルのロードとモジュールとは直交する概念ですが、 実用的にはモジュール毎にファイルを分割するのが便利です。 必ずしもそうする必要は無く、requireimport を別々に 使っても構いません。が、Gaucheに附属してくるライブラリはすべて、 use マクロで使えるように書かれています。

もしモジュールが一つのファイルに収めるには大きすぎる場合、一つのメインファイルと いくつかのサブファイルに分けることも出来ます。メインファイルの中でモジュールを 定義し、サブファイルをまとめてロードするか、オートロードを設定します。

実際は、与えられたモジュール名からファイルのパス名を得るのに 手続きmodule-name->pathが使われます。デフォルトの変換規則は、 モジュール名name中のピリオド‘.’を‘/’に置換 するというものです。例えば(use foo.bar.baz)

(require "foo/bar/baz")
(import foo.bar.baz)

となります。これはあまりScheme風ではありませんが、便利ではあります。 将来、このマッピングルールをカスタマイズする機構が導入されるかもしれません。

useされるファイルがトップレベル定義を持つ場合、ファイル内でモジュールが 明示されてることが必要です (通常はdefine-module/select-moduledefine-libraryが使われます)。 もし、“Attempted to create a binding in a sealed module: module: #<module gauche.require-base>”というエラーが出たら、 useしたファイルがモジュール指定を持っていないということです。 詳しくはrequireとprovideを参照してください。


Next: , Previous: , Up: モジュール   [Contents][Index]

4.13.5 モジュールの継承

export-importメカニズムは、次のような場合をうまく処理できません。

このような場合にモジュールの継承が使えます。

Macro: extend module-name …

カレントモジュールが、module-name …に挙げられたモジュールを 継承するようにします。それまでの継承の情報は捨てられ、module-name … から計算される継承情報が有効になります。

新たに作られるモジュールはデフォルトでgaucheモジュールを継承しています。 例えばそのモジュールに(extend scheme)というフォームを入れた場合、 その時点でそのモジュールはschemeモジュール(R5RSで定義された束縛 のみを含む)を直接継承するようになります。したがって、そのフォームの後で ’import’ やその他gauche特有の束縛はそのモジュール内では 使えなくなります。

module-nameに挙げられたモジュールがまだ存在しなかった場合、 extenduseと同じメカニズムを使ってファイルをロードすることを 試みます。

モジュールは複数のモジュールを継承することができます。 丁度、クラスが複数のクラスを継承できるのと同じようにです。 多重継承の場合、次のようにしてモジュール間の優先順位が決められます。

各モジュールは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.basemylib.utilmylib.systemに分けて 書いたとしましょう。次のように書けば、これらのモジュールを 一つのmylibモジュールに見せることができます。

(define-module mylib
  (extend mylib.system mylib.util mylib.base))

このライブラリモジュールのユーザは (use mylib) とするだけで 全てのサブモジュールのexportされた束縛を利用することができるようになります。


Next: , Previous: , Up: モジュール   [Contents][Index]

4.13.6 モジュールイントロスペクション

この節では、実行時にモジュールを操作する手続きをリストします。 これらの手続きにより、例えばモジュールの内部を調べたり、手続き的に 新しいモジュールを作成したり、特定のモジュールやライブラリの存在を 調べたりすることができます。ただし、モジュールは第一にコンパイル時の 構造であることを忘れないでください。実行時にモジュールをいじくるのは、 十分にモジュールの構造を理解した上で行ってください。

Builtin Class: <module>

モジュールクラスです。

Function: module? obj

objがモジュールなら真の値を返します。

Function: find-module name

名前がシンボルnameであるようなモジュールを返します。 その名前をもつモジュールが存在しなければ、#fを返します。

Function: make-module name :key if-exists

シンボルの名前nameを持つモジュールを作成して返します。 その名前を持つモジュールが既に存在していた場合、その動作は if-existsキーワード引数で指定されます。 if-exists引数が:errorである場合(デフォルト)、 エラーが報告されます。それが#fである場合は単に#fが返されます。

モジュールを実行時に動的に生成することは、通常のスクリプトでは あまり必要とはされません。既に書かれたプログラムの解釈においては、 モジュールは名前で指定されている必要があるからです。 構文define-moduleimportextendwith-module 等はモジュールそのものではなくモジュール名を取ります。 これは、モジュールが本質的にコンパイル時の構造であるためです。 しかし、動的に作られるモジュールが有用な場合もあります—プログラムそのものが、 動的に作られる場合です。evalにモジュールを渡して、 そのような動的に作られたプログラムがそのモジュールの中で コンパイルされ評価されるようにできます。

また、name#fを渡すことで無名のモジュールを作ることもできます。 無名のモジュールはfind-moduleで探すことはできませんし、 他のモジュールからimportすることもextendされることも できません(importextendはモジュール名を必要とするからです)。 無名のモジュールは、一時的に隔離された名前空間を動的に作りたい時に 便利です。例えばネットワークで接続されたプログラムから送られた式を その中で評価して、コネクションが終了したら名前空間ごと捨ててしまうという ような場合です。無名のモジュールはシステムの内部辞書に登録されないので、 モジュールへの参照が無くなればガベージコレクトされます。

R7RSでは、environment手続きによって一時的なモジュールを 作ることもできます。R7RS evalを参照してください。

Function: all-modules

現在存在する全ての名前付きモジュールのリストを返します。 無名のモジュールは含まれません。

Function: module-name module
Function: module-imports module
Function: module-exports module
Function: module-table module

モジュールオブジェクトのアクセスメソッドです。 moduleの名前(シンボル)、moduleがインポートしているモジュールのリスト、 エクスポートしているシンボルのリスト、そして シンボルから束縛へのマップを行うハッシュテーブルを返します。

もしmoduleが全てのシンボルをエクスポートしている場合は、module-exports#tを返します。

モジュールオブジェクト以外が渡された場合はエラーになります。

Function: module-parents module
Function: module-precedence-list module

モジュールの継承に関する情報を返します。 module-parentsmoduleが直接継承しているモジュールのリストを 返します。module-precedence-listmoduleのmodule precedence list (モジュールの継承参照) を返します。

Function: global-variable-bound? module symbol

symbolのグローバルなバインディングがmoduleから 可視であれば、真を返します。moduleはモジュールオブジェクトか 既存のモジュール名を示すシンボルでなければなりません。

註: 以前、この手続きの機能はsymbol-bound?という手続きで 実現されていました。symbol-bound?は非推奨となり、新しいコードは global-variable-bound?を使わねばなりません。 この変更の理由は、symbol-bound?がカレントモジュールをデフォルトと しており、またその名前からも、グローバルな束縛値があたかも (CommonLispのように)シンボルそのものの属性であるかのような誤解を招いて いたからです。そのせいで、特にコンパイル時と実行時でカレントモジュールが 異なるような場合に多くの混乱が生じていました。 新しい名前とAPIは、グローバルな束縛値についてモジュールに問い合わせている ということを明確にしています。

Function: global-variable-ref module symbol :optional default

モジュールmoduleから可視の、シンボルsymbolのグローバルな 束縛値を返します。moduleはモジュールオブジェクトか 既存のモジュール名を示すシンボルでなければなりません。 symbolに対する可視のグローバル束縛が無い場合は、 default引数があたえられていればその値を返し、 無ければエラーを通知します。

Function: module-name->path symbol

モジュール名symbolを、パス名の一部(requireprovideが 使うような)へと変換します。

Function: path->module-name string

module-name->pathの逆関数です。

特定のライブラリやモジュールがシステムにインストールされて使える状態にあるか 調べたりする場合は、ライブラリの操作を参照して下さい。


Previous: , Up: モジュール   [Contents][Index]

4.13.7 組み込みモジュール

Gauche起動時にいくつかのモジュールがあらかじめ定義されています。

Builtin Module: null

このモジュールはR5RSで述べられている"null environment"に相当します。 R5RSの構文要素への束縛だけを含んだモジュールです。

Builtin Module: scheme

このモジュールはnullモジュール内の束縛全てに加えて、 R5RSで定義されている全ての手続きの束縛を含みます。

select-moduleによって一度nullschemeモジュールに 入ると、そこから他のモジュールに移ることはできなくなることに注意してください。 これらのモジュールからは、あらゆるモジュール操作構文が不可視だからです。

Builtin Module: gauche

このモジュールはschemeモジュール内の全ての束縛に加え、 Gaucheの組込み手続きや構文が含まれています。

Builtin Module: user

このモジュールはユーザコードがコンパイルされる既定のモジュールです。 gaucheモジュール内の全ての束縛がインポートされています。

Builtin Module: gauche.keyword
Builtin Module: keyword

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_KEYWORD_IS_SYMBOLがセットされている状態ではキーワードも 単なるインポートされるシンボルにすぎないということを覚えておいてください。 例えば次のコードではGaucheの組み込み識別子をgauche/というプレフィクス つきでインポートしています。この場合、gauche.baseの継承経由で インポートされているキーワードもプレフィクスがつけられることになります。 キーワードをいちいちプレフィクスつき、もしくはクオートつきで書きたくなければ、 gauche.keywordを別にインポートしましょう。

;; R7RS program
(import (scheme base)
        (prefix (gauche base) gauche/) ; use gauche builting with gauche/ prefix
        (gauche keyword))              ; imports keywords

;; Without importing gauche.keyword,
;; you need to write ':directory
(gauche/sys-exec "ls" '("ls" "-l") :directory "/")

Previous: , Up: モジュール   [Contents][Index]