uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

てっとり早くGroovyでJava8のLambdaを使う

追記
http://groovy.codehaus.org/Groovy+2.3+release+notes#Groovy2.3releasenotes-OfficialsupportforrunningGroovyonJDK8

groovy 2.3以降、Goovyクロージャはfunctinal interface(sum型)が要求される場所で使用される場合、自動的にそのインターフェースに変換されるようになりました。

Groovy-MLで議論も始まっており、Groovyは将来的にはJava8正式対応するかと思いますが、世の中ではJava8アーリーアクセス版とかで話題になってるので、試してみました。

シンタックスのレベルでは、当然GroovyはLambda式を扱えませんが(決まってないし!)、Lambda式の実装がSAM型のサブクラスのインスタンスであることを踏まえると、以下のように例えばExpandoMetaClassでアダプタを定義してやれば、クロージャでJava8のStreamを叩けるわけです。

import java.util.function.*
import java.util.stream.Stream

class StreamTest {
  static test01() {
    List<String> names = ["hoge hoge", "foo bar", "junji", "uehara"]
    names.forEach{ println it }
  }
  
  static test02() {
    List<String> names = ["hoge hoge", "foo bar", "junji", "uehara"]
    names.stream()
      .filter { it.length() > 5}
      .map {"[" + it + "]"}
      .forEach{ println it }
  }
  
  static main(args) {
    Iterable.metaClass.forEach = { Closure clos -> delegate.forEach(new Consumer(){ void accept(arg){ clos.call(arg) }}) }
    Stream.metaClass.forEach =  { Closure clos -> delegate.forEach(new Consumer(){ void accept(arg){ clos.call(arg) }}) }
    Stream.metaClass.filter = { Closure clos -> delegate.filter(new Predicate(){ boolean test(arg){ clos.call(arg) }}) }
    Stream.metaClass.map = { Closure clos -> delegate.map(new Function(){ Object apply(arg){ clos.call(arg) }}) }
    test01()
    test02()
  }
}

もちろんこんなことしなくても上のレベルはGroovyだけでできるし、無限長コレクションを使いたければGroovy Stream(github)というのもあるんだけど、上はあくまでJdkAPIのStreamをGroovyのクロージャで利用している、というところがポイントです。

先のMLでの議論にあるように、クロージャとLambda式では得失がある*1ので、将来のGroovyにおいて、クロージャとLambda式が併存するのか、クロージャに一本化するべきなのか、その逆か、実装は、などはこれからの議論ということになるのでしょう。

使用したJava8のバージョンは

java full version "1.8.0-ea-lambda-nightly-h4200-20130429-b88-b00"

です。

参考:

*1:例えばGroovyのクロージャにおけるローカル変数のキャプチャは実効finalとかではなくリファレンスを経由してアクセスするので、値を変更できる代りにローカル変数アクセス時のオーバーヘッドがある