No Programming, No Life

プログラミング関連の話題や雑記

GroovyでメソッドをMethodClosureとして取得する

".&"を使って、引数が2つあるメソッド(二つ目はクロージャ)をMethodClosureとして取得して使いたいんだけどうまく行かない。
(動作確認: Groovy Version: 1.5.7 JVM: 1.6.0_10)

うまくいかない例
def list = [1,2,3] 
def methodClosure(list, yield){
   list.each{ yield it }
}
// 普通に呼び出し
methodClosure(list){ println it }
// メソッドクロージャとして取得し呼び出し
this.&methodClosure.collect{ it * 2 }.sum()
エラー


groovy.lang.MissingMethodException: No signature of method: Script0.methodClosure() is applicable for argument types: (org.codehaus.groovy.runtime.IteratorClosureAdapter) values: {org.codehaus.groovy.runtime.IteratorClosureAdapter@182a70}
at Script0.run(Script0:8)

これは多分、ここのコメントでid:nobusueさんが

".&"はメソッドをクロージャとして取り出すための演算子ですので、もともとのメソッド自体はクロージャではありません。

のようにおっしゃっているように、クロージャとして呼び出すから引数の種類とか数には制限があるってことかな?

ちなみに、これだと動く

def methodClosure2(yield){
   list = [1,2,3]
   list.each{ yield it }
}
// 普通に呼び出し
methodClosure2{ print it }
// メソッドクロージャとして取得し呼び出し
this.&methodClosure2.collect{ it }.sum()

追記 (2009-01-28)

そもそも私の最初にしたかったことは、

def methodClosure(list, yield){
   list.each{ yield it }
}

のようなメソッドがある時に、メソッドをメソッドクロージャとして受け取り、

this.&methodClosure([1, 2, 3]){ it * 2 }

のように、例えば値を2倍するクロージャを引数として渡した場合に結果として [2, 4, 6] を受け取りたかったのですが、うまくいきませんでした。上記では結果が [1, 2, 3] となってしまいます。
これは、yieldとしてクロージャを受け取っているが、メソッドの中で受け取ったyieldに対してlistの各要素(it)を中継しているだけなのが原因のようです。テストとして以下のようにしても結果は常に [1, 2, 3] となります。

assert [1, 2, 3] == this.&methodClosure([1, 2, 3]){}
assert [1, 2, 3] == this.&methodClosure([1, 2, 3]){ it * 2 }
assert [1, 2, 3] == this.&methodClosure([1, 2, 3]){ it * 3 }
assert [1, 2, 3] == this.&methodClosure([1, 2, 3]){ it * 4 }

メソッド内で最後に評価されるのがlistなので、引数で渡したlistがそのまま返ってくるので常に [1, 2, 3] なんですかね?


で、ゲンゾウさんよりコメントで助言のあったようにメソッドの引数をあわせると、確かに呼び出すことは可能になりました。
↓こんな感じ。*1

this.&methodClosure([1, 2, 3], {}).collect({it * 2})

これも常に値は [1, 2, 3] となってしまいます。


ちなみに当初やりたかった処理は、以下のような感じで書くと実現できました。

def methodClosure4(list, yield){
   list.collect{ yield(it) }
}
assert [2, 4, 6] == this.&methodClosure4([1, 2, 3]){ it * 2 }

eachじゃなくてcollectにして、最後の評価結果をListにしたらうまくいきました。


ところで、メソッドクロージャの場合、以下のように引数のクロージャを省略しても起動できるようです。

def met(yield){
  (0..2).each{ yield "(${it})" }
}
assert this.&met.collect{ it * 2 } == ["(0)(0)", "(1)(1)", "(2)(2)"]

結果から推測すると、['(0)', '(1)', '(2)'] が返ってきて、そのリストに対して続けざまにcollect*2するみたいな動きなのかな?これ。
うーん。動きが複雑。

*1:分かりやすくクロージャを括弧の中に入れてあります。

*2:文字列を2回繰り返す。