Shiro Kawai
shiro****@lava*****
2003年 11月 21日 (金) 21:48:38 JST
From: yasuy****@javao***** Subject: [Gauche-devel-jp] module名を文字列で指定してロードしたい Date: Fri, 21 Nov 2003 18:14:08 +0900 > dbi側の dbi-make-driver では、 > 文字列引数で指定されたモジュールを動的にロードして、 > そのモジュールの dbd-make-driver を呼んで > 各ドライバーのインスタンス (上の例だと <pg-driver> 型)が返るように > プログラムしたいです。 もう一つ別のアプローチがあるのですが、それは後で述べます。 > (define-generic dbi-make-driver) > (define-method dbi-make-driver ((driver-name <string>)) > (define module-name (string-append "dbd-" driver-name)) > (load module-name) ;; loadでいいのか..? > (dbd-make-driver driver-name)) > > dbd-pgでは、 > > (define-method dbd-make-driver ((driver-name <string>)) > (make <pg-driver> :driver-name driver-name)) これはまずいです。dbd-make-driverのシグネチャが各dbd-*で 重なってしまうので、後から定義した方が前の定義を上書きしてしまいます。 そもそも、dbd-make-driver自身はポリモルフィックに振る舞う 必要が無いので、generic functionではなく普通の手続きで 良いです。 > と書いていますが、実行時に > > *** ERROR: unbound variable: dbd-make-driver > Stack Trace: > _______________________________________ > 0 (dbi-make-driver "pg") > At line 5 of "(stdin)" これはモジュールのインポートの関係で何かが見えていないんじゃないかと 思いますが、コードを見ないとわかりません。 > モジュール名を文字列で指定して use や require や import する > 方法はあるのでしょうか...? 今のところは、evalを使うしかないですね。 最も手っ取り早いのはこんな感じでしょうか (説明の簡略化のため、 エラーチェック等は省いてあります)。 (define (dbi-make-driver driver-name) (let ((module-name (string->symbol #`"dbd.,driver-name")) (class-name (string->symbol #`"<,|driver-name|-driver>"))) (eval `(use ,module-name) (current-module)) (let ((driver-class (eval class-name (current-module)))) (make driver-class :driver-name driver-name)))) このコードだとdbi-make-driverが定義されているモジュール(dbi)に どんどん新しいモジュールがインポートされてしまうのがちょっと気持ち 悪いので、requireだけしておいて、直接dbd.*内の定義を覗く方法もあります。 私はこっちの方が好みかな。 (define (dbi-make-driver driver-name) (let* ((mod-name (string->symbol #`"dbd.,name")) (lib-name (module-name->path mod-name)) (class-name (string->symbol #`"<,|driver-name|-driver>"))) (eval `(require ,lib-name) (current-module)) (let ((driver-class (eval class-name (find-module mod-name)))) (make driver-class :driver-name driver-name)))) loadを使うのは避けた方が良いでしょう。loadは実行するたびに ファイルをロードしてしまうので、モジュール内のスタティックデータが 毎回リセットされてしまうとか、複数のスレッドが同時にloadを発行した 場合にシステム側では排他制御をしないといった問題があります。 requireは複数スレッドが同時に実行しても、また複数回実行しても、 一度だけファイルを読み込むことが保証されています。 なお、上で言った「もうひとつのアプローチ」とは、アプリケーション側に モジュールのロードを任せて、ライブラリにはクラスオブジェクトを受け渡す という方法です。(dbm.* 系がその方針で作ってあります) 実際にアプリを書いていると、どうしても使うモジュールが実行時まで わからないということはあるので、どこかのレイヤで文字列→モジュール というマッピングは必要になるでしょう。(例えば、コマンドライン引数で データベースタイプを受け取るとか、設定ファイルから読み込むとか)。 ただ、(eval `(require ,...)) をやってしまうと、アプリがどの ファイルを使うのかを静的に追跡することが不可能になります。 ライブラリレベルでそれをやると、そのライブラリを使った全ての ライブラリにその性質が伝播してしまいます。どうせやるなら、 なるべくアプリに近い側(mainを含むファイル)でやるほうが良いんじゃ ないかなあ、というのが、私が持っている印象です。 この印象が正しいかどうかはわかりません。結局避けられないのなら ライブラリ側でサポートしてしまった方が便利かもしれません。 あるいは、実行時のrequire/importを行う共通APIをひとつ作って、 全てをそこに集約させるという手もあるかもしれません。 ファイルを静的に追跡できると、例えば外部ファイルに依存しない 実行可能ファイルなんかを作る時に便利だろうなとはぼんやり 思っているのですが、先まで見えているわけじゃないです。 --shiro