Groovy MOP
Groovy にはメタプログラミングための API が用意されている。*1
関数の自動メモ化 の中で当てはめると Spring AOP のようなことを言語内で行える。
class MemoizeInterceptor implements Interceptor { def cache = [:] def hasResult @Override Object beforeInvoke(Object object, String methodName, Object[] arguments) { this.hasResult = cache.containsKey([methodName, *arguments]) } @Override Object afterInvoke(Object object, String methodName, Object[] arguments, Object result) { def key = [methodName, *arguments] if (!cache.containsKey(key)) cache[key] = result cache[key] } @Override boolean doInvoke() { !this.hasResult } } def memoize(obj, Closure blk) { def proxy = ProxyMetaClass.getInstance(obj.class) proxy.interceptor = new MemoizeInterceptor() proxy.use(obj, blk) } def fib = { n -> println n; n <= 1 ? n : call(n - 1) + call(n - 2) } memoize(fib) { assert fib(40) == 102334155 }
再帰呼び出しも上書きできてしまう。
Groovy 1.8 になって動作が変わっている部分
Groovy v1.8の新機能をサクっと紹介するよ - No Programming, No Life を見て新機能に目がいって気づかなかったがパフォーマンスの向上の部分だ。
"this"でのメソッド呼び出し時の引数がぴったり一致する場合、メソッドはダイレクトに呼び出されるようになります。なので、これは実行時のメソッド動的呼出しの際には行われません。
Groovy イン・アクションの GroovyInterceptable 例がエラーになった。
import org.codehaus.groovy.runtime.StringBufferWriter import org.codehaus.groovy.runtime.InvokerHelper class Traceable implements GroovyInterceptable { Writer writer = new PrintWriter(System.out) private int indent = 0 Object invokeMethod(String name, Object args){ writer.write("\n" + ' '*indent + "before method '$name'") writer.flush() indent++ def metaClass = InvokerHelper.getMetaClass(this) def result = metaClass.invokeMethod(this, name, args) indent-- writer.write("\n" + ' '*indent + "after method '$name'") writer.flush() return result } } class Whatever extends Traceable { int outer(){ return inner() } int inner(){ return 1 } } def log = new StringBuffer() def traceMe = new Whatever(writer: new StringBufferWriter(log)) assert 1 == traceMe.outer() println log assert log.toString() == """ before method 'outer' before method 'inner' after method 'inner' after method 'outer'"""
inner の呼び出しが invokeMethod を経由しないで直接呼び出されるようになったので trace できていない。
disableopt オプションで最適化を off にしたら動作した。
もう一つの Interceptor の例は訳注に 1.1 の時点でエラーになったと書いてあるが 1.8 でも動作した*2。
MOP2
Groovy イン・アクションの時点で MOP の設計の見直しがあると書いてあるが変わっていない気がする。
Groovy to infinity and beyond - GR8Conf Europe 2010 - Guillaume Lafor…
去年の資料だが MOP の部分は抽象的に書かれているので Groovy 2.0 以降になるのかな。
検索してもあまり使用例が見つからないのだけど現在の MOP でもかなりのパワーを秘めていると思う。
2011-05-08 追記
ブロックに引数で渡してみた。
def memoize(obj, Closure blk) { def proxy = ProxyMetaClass.getInstance(obj.class) proxy.interceptor = new MemoizeInterceptor() proxy.use(obj) { blk(obj) } } memoize({ n -> println n; n <= 1 ? n : call(n - 1) + call(n - 2) }) { assert it(40) == 102334155 }
*1:Groovy イン・アクション 7.6。 Google ブックスで公開されている
*2:スペースの数は調節した