先週購入したこんな本を読んでいて、Groovyであんまり見慣れないコードが書いてあるのを見つけました。

実践プログラミングDSL ドメイン特化言語の設計と実装のノウハウ (Programmer’s SELECTION)
- 作者: Debasish Ghosh,佐藤竜一
- 出版社/メーカー: 翔泳社
- 発売日: 2012/06/08
- メディア: 大型本
- 購入: 4人 クリック: 82回
- この商品を含むブログ (14件) を見る
こういうやつです。
newOrder.to.buy(100.shares.of('IBM')) { limitPrice 300 allOrNone true valueAs {qty, unitPrice -> qty * unitPrice - 500} }
Closure内ではあるクラスのメソッド(limitPriceとかallOrNoneとか)を読んでいるのですが、レシーバーがないのにどうやってるんだっけ?とか、Closureが第2引数なのに括弧の外にあるよ?とか思って不思議に思っていました。一応自己解決したのですが、よくよく見るといつものGroovy本にも書いてありました。

- 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/06
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 392回
- この商品を含むブログ (155件) を見る
これのトリックは、Groovy JDKのObject#withとメソッドの最後の引数にClosureが来た場合の表記法を利用してますね。
Groovy JDKで拡張された、Object#with(Closure)を使用すると、引数で受け取ったClosure内でメソッドなどをwithのレシーバーとなったオブジェクトのメソッドやプロパティを、まるで自分で定義してあるかのように呼び出しできるみたいですね。
以下、ちょっと例。
class Person { String name def say() { println "I'm ${name}!" } def decorate() { "***${name}***" } } new Person(name: "Kazuhira").with { say() println(decorate()) }
実行結果。
I'm Kazuhira!
***Kazuhira***
なるほど、インスタンスを指定していないのも関わらず、withの呼び出しに使用したオブジェクトが持つメソッドが解決できていますね。
これのトリックは、Closure#delegateプロパティのようで、以下のようなコードでも同様の動きをさせることができます。
def clos = { say() println(decorate()) } clos.delegate = new Person(name: "Kazuhira") clos()
Closure#ownerは知っていましたが、全然関係ない書籍でClosure#delegateを理解することになりました(笑)。
続いて、メソッドに引数にClosureを取る場合ですが、Closureがメソッドの最後の引数となっている場合は、呼び出し括弧の外に置けるようです。よくScalaでは
def using[A <: { def close(): Unit }, B](resource: A)(body: A => B): B = { …省略… } using(new Resource) { r => …省略… }
みたいなコードを見かけると思いますが、これはカリー化をサポートしてないとできないと思っていたのでGroovyでは書けないと思ってましたわ。
例えば、こんな感じ。
def using(resource, closure) { try { closure(resource) } finally { resource?.close() } } class R { def put(v) { println v } def close() { println "Closed" } } using(new R()) { r -> r.put("Hello World") }
実行結果。
Hello World Closed
こちらもGroovy本に載っていました。が、さらっと書かれていたので流しちゃったんだろうなぁ…。
最後の引数がClosureであればよいので、その前の引数は複数あってもOKです。
def withClosure(v1, v2, closure) { println "v1 = ${v1}" println "v2 = ${v2}" closure() } withClosure("Hello", "World") { println("in Closure!") }
GroovyはScala、Clojureほど細かく勉強してませんし、軽く書けるJavaみたいな感じで使っているので、こういう細かいところは見落としてしまったんでしょうなぁ…。ちょっともったいなかったです。