今回はマジック・パッケージを使ったメタプログラミングを試してみます。
- 一応、よくやるメタ・プログラミング、復習
- マジック・パッケージでメタプログラミング
- パフォーマンスは果たして?
参考 URL
- Groovy Goodness 「Magic Package to Add Custom MetaClass」
一応、よくやるメタ・プログラミング、復習
Groovy では既存のクラスに新たにメソッドを追加したい場合、MetaClass にクロージャ型のプロパティをセットします。 例えば*1 apache commons-math の複素数を表す Complex クラスに対して、(演算子オーバーロードとして) plus メソッドを追加するには
Complex.metaClass.plus = { Complex arg -> delegate.add(arg) }
という風にします。 Complex オブジェクトにはもともと add メソッドが定義されてます(念のため)。 もう少し具体的には、
Complex.metaClass.plus = { Complex arg -> delegate.add(arg) } def c1 = new Complex(1, 2) def c2 = new Complex(3, 4) println(c1 + c2)
という風に + 演算子(plus メソッド)が実行できるようになります。
マジック・パッケージでメタプログラミング
では、マジック・パッケージを使った方法。 この方法では、決められた規約のパッケージ名・クラス名のメタクラスを作成することで、使用者が事前にコードを実行(metaClass.plus = ... のような)せずにメソッドを使用できるようになります。 まぁ、具体的にやってみましょう。
上記の例での Complex クラスはパッケージ org.apache.commons.math.complex に含まれているので、このクラスにメソッドを追加したい場合は
- groovy.runtime.metaclass.org.apache.commons.math.complex パッケージ
に ComplexMetaClass クラスを作成します:
package groovy.runtime.metaclass.org.apache.commons.math.complex class ComplexMetaClass extends DelegatingMetaClass { ComplexMetaClass(MetaClass meta) { super(meta) } Object invokeMethod(Object object, String method, Object[] args) { if (method == 'plus') { return object.add(args[0]) } else { super.invokeMethod object, method, args } } }
このクラスをコンパイルしてクラスパス上におくと、
def c1 = new Complex(1, 2) def c2 = new Complex(3, 4) println(c1 + c2)
というコードがそのまま実行できます。 ちなみに、この方法はコンパイル前提だそうです。
パフォーマンスは果たして?
マジック・パッケージを使った上記の方法では invokeMethod メソッドを実装しているので少々パフォーマンスが心配。 ってことで簡単に実行時間を測ってみましょう。
で試してます。
通常のメソッド
まずは通常のメソッドを使う場合。 複素数の足し算を1,000,000回実行しています。
import org.apache.commons.math.complex.Complex perform{ int n -> def c1 = new Complex(n, n) def c2 = new Complex(n, n) c1.add(c2) } def perform(Closure c){ long start = System.currentTimeMillis() 1_000_000.times(c) long end = System.currentTimeMillis() println(end - start) }
この実行時間を1としましょう。
メタクラスを使う
次はよくやるメタプログラミングの方法。
import org.apache.commons.math.complex.Complex Complex.metaClass.plus = { Complex arg -> delegate.add(arg) } perform{ int n -> def c1 = new Complex(n, n) def c2 = new Complex(n, n) c1 + c2 } def perform(Closure c){ ... }
perform メソッドは先ほどと同じ。 実行時間は約1.7でした。 おっ、Groovy 結構速くなってんじゃない!?
マジック・パッケージ
で、最後はマジック・パッケージを使った方法。
import org.apache.commons.math.complex.Complex perform{ int n -> def c1 = new Complex(n, n) def c2 = new Complex(n, n) c1 + c2 } def perform(Closure c){ ... }
この方法の実行時間は約4.2。 ん〜、invokeMethod メソッドでコーディングしてる分、パフォーマンスが劣化してます。 さらに哀しいことに、この方法ではもともと Complex クラスに定義されている add メソッドを使っても同様のパフォーマンスの劣化があることです! add メソッドを呼び出しても ComplexMetaClass クラスの invokeMethod メソッドが実行されてるんでしょうね。 ComplexMetaClass をもうちょっと違った実装にすれば解決するのかも知れませんが(例えば DelegatingMetaClass 以外のクラスを拡張するとか)、 これはちょっと辛い。
- 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/06
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 392回
- この商品を含むブログ (152件) を見る
*1:どうも例えがマイナーなライブラリを使ったものになるのが申し訳ないですが。