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 のこと