Groovy で Emulating callable objects
Python では object.__call__ を定義するとオブジェクトを呼び出せるそうだ。
Scala でも apply メソッドで同じようにオブジェクトを呼び出すことができる。
Groovy で同じようにするにはどうすればよいか。
そもそもの問題
『7つの言語 7つの世界』で Prolog の章を読んで面白かったのでいろいろ探していたらOKIソフトウェアの鈴木氏の Tiny Prolog シリーズを発見した。
他に Ruby, Java, C# 版もある。
理解するために Groovy に翻訳していた。
ここで object.__call__ が使われていて、別に Groovy では object._() のように目立たないメソッド呼出しにすればいいだけなのだが悔しいので粘って見た。
むしろ問題は Generator の方なんだけどこっちは検索して見たら Java で実装されたものが見つかったのでそれを使った。
Groovy の Cookbook Examples にも Generator が載っているがこれではうまく動かなかった。*1
ログが実行毎に違ったりしたのでスレッドの競合かもしれない。
でもコードを読んでも pullValue() と doCall() は排他できていてるように見える。
何故うまく動かないのか原因がわからない。
それは TODO にしておいて Groovy で Object を呼び出すにはどうすればよいか。
Closure#setDelegate(object)
Groovy では Object を呼び出すことはできないのだけど Closure が属している Object を変更できることを思い出した。
いつもは Builder で使うときのように Closure の内側から delegate を呼び出すが、外側からも呼び出せたのだ。
class Person { def name @Override String toString() { name } } def robert = new Person(name:"Robert") println robert println() println "// new Closure(owner /*, null*/)" bob = new Closure(robert) { def doCall() { "Hi!" } def doCall(String s) { "My name is $name." } // def doCall(String s) { "My name is ${this.name}." } Error @Override String toString() { "Bob" } } println bob println bob() println bob("What's your name?") println bob.name println() println "// new Closure(null, thisObject)" bob = new Closure(null, robert) { def doCall() { "Hi!" } // def doCall(String s) { "My name is $name." } Error def doCall(String s) { "My name is ${this.name}." } @Override String toString() { "Bob" } } println bob println bob() println bob("What's your name?") // println bob.name Error println() println "// Closure#setDelegate(obj)" bob = { "My name is ${name}." } // bob = { "My name is ${this.name}." } Error bob.delegate = robert println bob println bob() println bob("What's your name?") println bob.name println()
Robert // new Closure(owner /*, null*/) Bob Hi! My name is Robert. Robert // new Closure(null, thisObject) Bob Hi! My name is Robert. // Closure#setDelegate(obj) emulating_callable_objects$_run_closure1@2f60877b My name is Robert. My name is Robert. Robert
Closure の doCall をオーバーロードすれば呼び出しのパターンも増やせる。
ただし、instanceof で調べても Closure でしかないが今回は問題ない。
Person が Closure を継承するようにしてもよいが Closure は特別なクラスなので簡単には拡張できない。
Closure が static コンテキストで生成された場合
this に何が設定されているか気になったのでついでに調べて見た。
class A { def foo() { return {} } static def bar() { return {} } } def a = new A() assert a.foo().owner == a assert A.bar().owner == A assert a.foo().thisObject == a assert A.bar().thisObject == A assert a.foo().delegate == a.foo().owner assert A.bar().delegate == A.bar().owner
static コンテキストで生成された場合は、生成したクラスが設定されている。
追記 2012-02-25
環境は Groovy 1.8.6 で確認
『Groovy・イン・アクション』によると JSR になる前の Groovy では Closure 内の this は Closure 自身を参照していたらしい。
JSR エキスパートグループによって現在の形に変更されたそうだ。
Groovy 1.8.6 では Ruby の each_cons や each_slice の動作をする List#collate が追加されていた。
未だに startGroovy.bat の問題がそのままだが patch 当てれば OK
*1:掲載されているサンプルは動作する