Groovy で Emulating callable objects その2
前回の Closure よりもっと簡単にできた。
というか Python と同じだった。
Python と同じ方法
org.codehaus.groovy.runtime.ScriptBytecodeAdapter より
// TODO: set sender class public static Object invokeClosure(Object closure, Object[] arguments) throws Throwable { return invokeMethodN(closure.getClass(), closure, "call", arguments); }
つまり、call メソッドが実装されていれば呼び出せるようだ。
これなら Closure 以外のインスタンスとして扱える。
class HasCallMethod { def call(int i) { i+1 } } def obj = new HasCallMethod() assert obj instanceof Closure == false assert obj(1) == 2
ちなみに、Closure には doCall を呼び出す call メソッドが最初から存在するので doCall で拡張できる。
正規の方法かは不明だが、調べたら使っている人達もいるようだ。
前回の TODO: Generator が動作しなかった理由*1 *2
Iterator#next が呼ばれたから次の値を取得するのではなく、next の前に値が用意されることもある。
したがって、副作用を期待するとエラーになる。
N = 10 i = 0 N.times{ i++ } assert N == i i = 0 interator = new Generator({ yield -> while(true) { i++ println(i) yield(i) } }).iterator() N.times{ println "next()" interator.next() } assert i == N // error: N <= i <= N+2
出力
pullValue() と doCall() は同期の中にログを仕込んだ。
next() 1 doCall(1) 2 pulledValue = 1 doCall(2) 3 next() pulledValue = 2 next() doCall(3) 4 pulledValue = 3 next() doCall(4) 5 pulledValue = 4 next() doCall(5) 6 pulledValue = 5 next() doCall(6) 7 pulledValue = 6 next() doCall(7) 8 pulledValue = 7 doCall(8) 9 next() pulledValue = 8 next() doCall(9) 10 pulledValue = 9 next() doCall(10) 11 pulledValue = 10 doCall(11) 12
doCall() が呼ばれて pulledValue が呼ばれる順序は必ず守られる。
ただ pulledValue が参照されると次の doCall を呼び出してよくなり、yield でブロックされる前に評価されるので最大2ずれる。
fibonacci 数の計算では副作用がないのでうまく動作したが Tiny Prolog では env を副作用を参照するのでエラーになってしまった。
*1:エントリ書いたので前回への追記からこっちへ移動した
*2:http://groovy.codehaus.org/Iterator+Tricks の Generator のこと