[Gauche-devel-jp] Re: module名を文字列で指定してロードしたい

Back to archive index

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





Gauche-devel-jp メーリングリストの案内
Back to archive index