#:g1: Common Lispでmethod_missing

Posted 2022-09-10 22:05:15 GMT

なんとなく図書館からメタプログラミングRuby 第2版を借りて眺めていたのですが、Rubyが結構流行っていた頃にはmethod_missingのような機能を活用する手法も良く話題になっていたものでした。

Ruby等のメソッドがクラスに属しているタイプのオブジェクト指向システムでは、実行時にオブジェクトにメソッドが無い場合にmethod_missingのようなメソッドが呼ばれるようにしてあります。
method_missingをオーバーライドして、実行時にあれこれするという手法なのですが、Common Lispだと総称関数方式なので、オブジェクトが起点となってディスパッチをさせる仕組みはありません。
method_missingに似たものに、no-applicable-methodというのがありますが、総称関数が束ねるメソッドに適用可能なものが存在しない場合に呼ばれるメソッドであり、ちょっと毛色が違います。

しかし良く考えてみると、総称関数中でmethod_missingしていると考えれば、ソースコードの見た目は違えどRubyと同じようなことはできるなと思ったので試してみました。

お題は、メタプログラミングRuby 第2版でも紹介されているHashie::Mashというもので、ハッシュテーブルが任意の名前のアクセサを持つように拡張されたものです。
Rubyの場合は、ハッシュテーブルを、

foo[:bar]=42
foo[:bar] #42

のように記述することになりますが、これを

foo.bar=42
foo.bar #42

と書けると便利、ということらしいです。

method_missing を no-applicable-method で置き換えて再現

とりあえず、起点となる総称関数を定義する必要があるので、Mashの例が再現できるようにセッタとゲッタの基盤を定義します。

(defgeneric op (obj msg))
(defgeneric (setf op) (val obj msg))

これでハッシュテーブルの初期値を設定して呼び出すと、no-applicable-methodが発動するので、

(let ((tab (make-hash-table)))
  (setf (op tab 'foo) 42)
  (op tab 'foo))

デバッガ画面は開いたままで、no-applicable-methodを定義します

(defmethod no-applicable-method ((gf (eql #'(setf op))) &rest args)
  (destructuring-bind (val obj msg) args
    (typecase obj
      (hash-table (setf (gethash msg obj) val))
      (T (call-next-method)))))

(defmethod no-applicable-method ((gf (eql #'op)) &rest args) (destructuring-bind (obj msg) args (typecase obj (hash-table (gethash msg obj)) (T (call-next-method)))))

定義の後でデバッガから継続すれば、42が返ってきます。

ちなみに、デバッガが起動するようなことをする必要はなく、全部定義が済んでから実行すればいいだけなのですが、なんとなく動的な気分で進めてみました。

まとめ

先に総称関数を定義する必要がありますが、Common Lispでもmethod_missingができそうです。
Rubyではこのように動的に定義されるメソッドをゴーストメソッドと呼んだりするようですが、総称関数で考えてみた場合、役に立つ応用例が全く思い付きませんが、クラスを動的に定義するゴーストクラスのようなものが可能な筈です。


HTML generated by 3bmd in LispWorks 8.0.1

comments powered by Disqus