uehaj's blog

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

GroovyのクロージャとJava8のlambda式の違いについて

この両者は、似ているようでいて、基本的には別モノです。表にしてみます。

Groovyのクロージャ java8のlambda式
導入時期 2003年 2014年03月
ローカル変数へのアクセス 読み書き可能 実質的にfinal(変数そのものに対しては読み込みのみ)
実装方法 Closure型のインスタンス MethodHandle, invokeDynamic..
型推論の根拠 Closure<T>のTで返り値、@ClosureParamsで引数 FunctionalInterface(SAM型)
記法 { 引数 -> 本体 }
{ 本体 }
{-> 本体 }
(引数) -> { 本体 }
(引数) -> 式
() -> { 本体 }
暗黙の動的なthis delegateにより実現 -
性能 ローカル変数をenclosingするため間接参照にするためのオーバーヘッドあり 性能上のオーバーヘッド僅少

赤字がデメリット、青字が利点のイメージです。

このように、機能としては、Groovyクロージャの方が高機能だと言えます。特にローカル変数への書き込みアクセスが可能であることと、delegate名前空間を制御できるのがGroovyクロージャの強力な利点です。

他方、lambda式の方が優れているのは、一つは性能です。コンパイラが頑張っているのか、静的型の面目躍如でfor文にも負けぬパフォーマンスを叩き出すという話を聞いたことがあります。もともと並列化が目的だったようなので、遅くっちゃしょうがないのです。

もう1点、lambda式の方が優れている面があるのは、型推論に関することであり、本エントリのテーマでもあります。lambda式の方が型推論の効果を得るのが簡単です。

型推論に関して

どういうことかというと、たとえばクロージャもしくはlambda式を引数にとるメソッドsomethingがあったとします。
Javaなら

void something(Function<String, String> x) {
  System.out.println(x.apply("abc"));
}

こんな感じ、Groovyなら

void something(Closure<String> x) {
  println(x.apply("abc"))
}

です。これを呼び出すとき、それぞれ

something { String it -> it.toUpperCase() } // Groovy
something( (String it) -> { it.toUpperCase() } ) // Java8

これは問題ありません。しかし、以下のように型を指定せず、型推論を期待したとき、どうなるでしょうか。

something { it -> it.toUpperCase() } // Groovy
something( (it) -> { it.toUpperCase() } ) // Java8

Java

Javaでは問題ありません。somethingに渡す引数の型は、Functional Interfaceから判るからです。上であれば、クロージャ引数の型はFunction<String, String> xなので、引数の型が最初のString、戻り値の型は2番目の型変数でこれもStringです。

somethingを呼びだす時に渡すラムダ式の引数には、文字列型の値が渡ってくることがコンパイラは知ることができるので、itの型を省略してもStringであることが推論されます。

Groovy

Groovyでも動的Groovyであれば、すなわち@CompileStaticや@TypeCheckedを指定しなければ、問題ありません。.toUpperCase()が呼び出されるオブジェクトが実行時点でStringであれば良いからです。

しかし、静的Groovy、すなわち@CompileStaticや@TypeCheckedを指定した場合、クロージャの引数の型を省略した

    something { it -> it.toUpperCase() }  // Groovy

は静的型チェックについてのコンパイルエラーになります。

[Static type checking] - Cannot find matching method java.lang.Object#toUpperCase(). Please check if the declared type is right and if the method exists.
@ line 11, column 24.
something( { it -> it.toUpperCase() } ) // Groovy

これはコンパイル時に、クロージャ「{ it -> it.toUpperCase() }」の引数itの型がわからないからです。
Closure<T>というクロージャ引数から、Tは返り値の型でわかりますが、クロージャに渡される引数の情報はなく、引数の型がわかりません。

これをコンパイルエラーにならなくするにはクロージャの引数型を明示指定するか、あるいはsomethingがわを以下のようにします。

@TypeChecked
void something(@ClosureParams(value=SimpleType,options=['java.lang.String']) Closure<String> x) {
  System.out.println(x.call("abc"));
}

@ClosureParamsアノテーションで、クロージャに渡す引数の型を明示します。ここでは単純にString型をクラス名FQCN文字列で指定しています。他に、渡した他の引数の型や、ジェネリックスの型などを指定することもできます。

なんでこうなった

GroovyのクロージャがもともとFunctional Interfaceを前提としていないことがあります。その理由は、クロージャ導入当時は型推論なんかないので、クロージャの引数の型を指定する方法は不要だったからです。

では今なぜFunctional Interfaceを導入しないのか?

一つは、Groovyでは、引数に渡すクロージャの引数の型や個数によって、動作をかえることができるからとのことです。たとえば、Map.each()には、{k,v ->}というクロージャを渡すとkey,valueが、{e ->}というクロージャを渡すとMap.Entryが引数としてわたってきます。これをPolymorphic Closureと呼ぶそうです。この2つのいずれか、という直和型をGroovy/Javaの型システムはうまく表現できないでしょう。またこの理由に加え、おそらく後方互換性的な理由などもあって選択されなかったのではないかと想像しています。

いずれにせよ、他にもdelegate型推論の問題もあるので、仮にFunctional Interfaceを導入したとしても型推論の問題は全体としては解決しません。delegateの問題については一部それを解決しようとする@DelegatesToアノテーションに関するこちらの記事も参照ください。

ターゲット型推論

あと、Java8のlambdaの方ではターゲット型推論がたぶん良く効きます。その影響は、えーと良くわかりません。眠いから調査はやめときます。

相互運用

FunctionalInterfaceが求められる箇所でGroovyクロージャを使用すると、Closureのcall()を呼びだすようなlambda式が生成されてそれが渡されます。Groovy2.1だったかな?そこらでこれが可能となりました。実用上、これで良しとされています。Javaからlambda式をクロージャとして渡せるかというのはガリガリ書くことがたぶんできますが言語処理系のサポートはまったくありません。

まとめ

現在は、クロージャ引数を型推論させたいとき、つまり静的Groovyをしつつ型を省略したいときは、@ClosureParamsを使う必要があり、これはラムダ式における指定方法Functional Interfaceより煩雑ですが、ライブラリ側でしっかりとやれば、それを呼び出す側は簡潔かつ実行時型エラーフリー*1になります。GDKのクロージャをとるメソッド群ではしっかり指定がなされていますが、自分で定義する関数についても適用できます。

GroovyへのLambdaの導入

なお、過去のMLを見ると、GroovyでもJava8 lambdaを導入するってのはGroovy 3での開発予定(希望)項目ぐらいにはあがってたみたいです。しかし、もしやるとしても構文や意味、統合するのか併存か、互換性維持など、思いきりチャレンジングでしょうね。個人的には頭痛くなるので現状ぐらいでお腹いっぱいです。

参考

uehaj.hatenablog.com
uehaj.hatenablog.com
uehaj.hatenablog.com

*1:完全にかは不明。ターゲット型推論について要調査。

オブジェクト指向だけじゃない開発宣言

Manifesto for Not Only Object-Oriented Developmentオブジェクト指向だけじゃない開発宣言、というのが出てましたのでごにょと訳してみました*1

オブジェクト指向だけじゃない開発宣言

私たちは、ソフトウェア開発の実践
あるいは実践を手助けをする活動を通じて、
よりよい開発方法を見つけだそうとしている。
この活動を通して、私たちは以下の価値に至った。


クラスよりも、関数と型を、
可変性よりも、純粋性を、
継承よりも、コンポジションを、
メソッドディスパッチよりも、高階関数を、
nullよりも、Optionを、


価値とする。すなわち、左記のことがらに価値があることを認めながらも(但しnullは除くが)、私たちは右記のことがらにより価値をおく。

内容は、「今どきのオブジェクト指向開発宣言」と名乗ってもあんまり違和感ないっす。
ただし、null、おめーは別だ。

追加希望
正格評価よりも、非正格評価を、
チューリングマシンではなく、SKコンビネータを、

追記:この翻訳が本家の日本語訳に採用されました。

*1:元ネタがアジャイル開発宣言のパロなので、訳も借用させてもらいました。

Groovy 2.3.0-betaが出たのでtraitを触ってみたメモ

Groovy 2.3.0-beta-1beta-2が出たので新機能traitをかるく触ってみました。

注意! 以下は現時点で2.3.0-beta-1と2の振舞いとドキュメントを調べた限りの情報です。正式リリースに向けて変更される可能性があります。

traitの概要と目的

Groovyのtraitは、一言で言って「実装の多重継承」を可能とする仕組みです。詳しくはこちらの本家ドキュメント(英語)をどうぞ。

GroovyおよびJava 7までのJavaでは、インターフェースは多重継承することができましたが、クラスは多重継承できませんでした。実装、すなわちメソッド本体の定義や、非public static finalなフィールド(インスタンス変数)定義はクラスでのみ可能であり、そしてクラスの継承は単一継承のみ(親が1つだけ)が可能なので、実装の継承は、ツリー型に制限されていました。北斗真拳と南斗聖拳の系列があったときDAG型の「両方の継承者」という統合はできないわけです(少なくとも実装の意味では。ジャギは実装は継承してない。上っ面のインターフェースのみです謎)。とにかく、いままでは実装の多重継承はできなかったということです。

Groovyのtraitでは以下の両方ができます。

  • クラスのようにメソッド本体やフィールドを定義する
  • インターフェースのように多重継承する

結果として、実装の多重継承ができるようになりました。

Java8のインターフェースでは、デフォルトとしての実装(メソッド本体)が定義でき、実装の多重継承もできるので、近いものがありますが、以下のような差異があります。

  • groovyのtraitではインスタンス変数(フィールド)も定義できる。つまりtraitで定義されたメソッド群の間でインスタンス固有のデータを共有・保持できる。(Scalaのtraitも同様)
  • groovyのtraitは、メソッドのデフォルト実装を定義するだけではなく、親traitのメソッドをオーバーライド定義することもできる。
  • groovyのtraitで定義したメソッドは、親クラスや他のtraitのメソッドに優先する(@ForceOverride使用。(Groovy.2.3正式版では@ForceOverrideはデフォルトかつ必須の動作になりアノテーションは削除された。) Scalaのabstract overrideの動作)
  • groovyのtraitはJava 8以前のJava VM(Java 6,7..)でgroovyを実行する場合でも利用できる(Scalaのtraitも同様)

GroovyのtraitはScalaのtraitと極めて良く似ています。traitの定義と静的な使用についてはscalaのそれとほぼ同様です*1。差異は、主に動的なtraitの実装に関するところであり、後述します。

表にまとめるとこんな感じ。

Java/Groovyクラス Java(〜Java7)およびGroovyのインターフェース Java8以降のインターフェース Groovyのtrait Scalaのtrait
定義に使用するキーワード class interface interface trait 同左
実装の単一継承 × ○(メソッドのデフォルト実装のみ) ○(フィールド使用・定義およびtrait側メソッド優先も可) 同左
実装の多重継承 × × ○(メソッドのデフォルト実装のみ) ○(フィールド使用・定義およびtrait側メソッド優先も可) 同左

コード例

たとえばこんな感じです。

trait A {}

trait B extends A {}

trait C extends A {}

class D implements B, C {}

詳しくはドキュメントをみてください。
以下もよろしければどうぞ。

www.slideshare.net

衝突!

さて実装の継承においては、多重継承だろうが単一継承であろうが、衝突というものを考慮する必要があります。

継承というのは親のフィールドやメソッドを引き継ぐということなので、子供は親のメソッドやフィールドを持っているかのように振る舞う必要があります。

単一継承であれば、お爺ちゃんと父で衝突すれば(名前が同じで実装が異なるようなものがあれば)、子供は(子供自身でオーバーライドしない限り)より近い祖先である父のものを持っているかのように振舞います。

多重継承の場合、加えて、父親(もしくはその祖先)と母親(もしくはその祖先)がそれぞれ同名で異なるメソッド実装やフィールドを持っているとき(=衝突)の考慮が必要ですが、子供はどっちのものを持っているかのように振る舞うべきでしょうか。

Groovyのtraitにおける衝突の解決もしくは回避

メソッド名に関しては、Groovyのtraitの衝突解決のデフォルトは指定したトレイトの順に、後勝ちです。つまり「implements 父,」もしくは後述の形式「withTrait(父,)」のように複数トレイトを親として指定したときに、所属トレイトを明示指定しないメソッド名が衝突していれば、指定がより後ろである側のメソッドが指定されたとみなされます。後勝ちが嫌な場合、子供でオーバーライド定義して明示的に後じゃない方を呼ぶこともできます(「トレイト名.super.メソッド名」で指定する)。ちなみにScalaでは潜在的に衝突しているとき、衝突しているメソッドを子供でオーバーライドしないとエラーになり、常に明示的な手動の衝突の解決が求められるそうですが、Groovyではそんな配慮は無いので「意図せざる偶然の衝突」に注意が必要になります。

コード例としてはこういうことです。

trait A { String foo(){"A"}}
trait B { String foo(){"B"}}

class C implements A,B {
}

class D implements B,A {
}

c = new C()
assert c.foo() == "B"
d = new D()
assert d.foo() == "A"

class E implements A,B {
    String foo(){A.super.foo()}
}

d = new E()
assert d.foo() == "A"

フィールドに関しては、フィールドの値を保持する変数名のリネームによって衝突が事前回避されます。トレイトで定義したフィールドは、そのトレイトの実装時に、

トレイトのFQCNの'.'を'_'に置換したもの+'__'+フィールド名

にリネームされた変数名のフィールドが、子クラス側で暗黙に定義されます。フィールドがprivateの場合も、publicの場合もいずれもです。子クラス側のインスタンスのフィールド名を直接指定する場合、後勝ちもクソもなく、このリネームされた名前で常に明示する必要があります。(リネームされる前、トレイト内のメソッドの定義においては、リネーム前の本来のフィールド名をソース記述上は使えます。しかしリフレクションとかでは違う名前になっているでしょうから注意)。

もっとも、フィールドがprivateであれば外部から指定することはできない(建前上不可視*2 )ので、問題になるとすればフィールドがpublicな場合のみでしょう。以下は例です。

trait A {
    public int a;
}

trait B extends A {
    public int b;
}

trait C extends A {
    public int c;
}

class D implements B, C {}

def d = new D()
println d.A__a
println d.B__b
println d.C__c

このようなフィールド名のリネームは、やや不自然に感じるかもしれませんが、フィールドを直接参照するのではなくgetter/setterを通じて扱えば、メソッド名の解決の話になりリネームされたフィールド名は隠蔽されるので、プログラマが意識することはなく、実際問題としては意識する機会はあまり多くないでしょう。

重要なのは、このリネームルールから、継承経路上に表われるすべてのトレイト実装は、そのFQCNによって単一化されるということです。ダイヤモンド継承(菱形継承)問題はこの形で解決されています。C++で言えば「仮想基底からの継承(仮想継承)」だけの扱いになるわけです。まあ、それでいいね。なんぼか直観的です。Scalaではどうなるかは知らない。

trait定義

classやinterfaceの代わりにキーワードtraitを使用します。それでだいたい期待通りに動作するでしょう。traitのコンパイル結果は、実体としては(クラスファイル上は)インターフェースといくつかの内部ヘルパクラス群になります。Javaからはトレイトはインターフェースとして見えるので、Groovyのtraitを実装しているGroovyのインスタンスJavaから扱えます。Groovyのtraitをtraitとして継承したクラスをJavaで定義するのはおそらく無理でしょう(traitを単なるインターフェースとしてJava側でimplementsすることはたぶんできる)。

静的なtrait実装

クラス定義時にトレイトを(インターフェースのように)implementsします。それでだいたい期待通りに動作するでしょう。

実行時のtrait実装

Groovyのtraitは、実行時にそれを実装したオブジェクトを作り出すことができます。Scalaも表面上似たことができるのですが、ここはGroovyとScalaで考え方が一番違うところです。

Scalaの場合を参考に

Scalaでtraitをnew時に実装するには、new..with構文を使用します。この構文は無名内部クラス構文によるインスタンスnewの拡張と言えましょう。無名内部クラスのように、内部的にクラスを生成した上でそのインスタンスを作るのです。Javaの無名内部クラスによるnewではできないこととして、複数のtraitを実装(with)する、対象クラスのサブクラスである無名内部クラスのインスタンスを生成します。これは完全に静的なものです。

たとえば、Xがクラス、Tがtraitだとしたとき

var x:X = new X() with T

であれば、xはXを継承したクラスのインスタンスであり、同時にトレイトTを継承(Scala的にはmixin)したインスタンスです。帰結として、finalクラスにはtraitを実装させることはできません。XがfinalならXのサブクラスが作れないからです。

Groovyでの実行時トレイト実装

Groovyでは、newと独立したタイミングで、既存の任意のインスタンスに対して、traitを実装した新規のプロキシインスタンス*3を作成します。

def x = new X() as T

こうです。トレイトが複数の時はこう。

def x = new X().withTrait(T1,T2)

new Xしてはいますが、そのインスタンスに対するメソッド呼び出しです。だから上はこうも書けます。

def tmp = new X()
def x = tmp as T
def tmp = new X()
def x = tmp.withTrait(T1,T2)

xはトレイトであるTやT1,T2を実装する、実行時に生成される動的プロキシのクラスのインスタンスです。Scalaとの重要な違いとして、このときのtmpとxはインスタンスが別で、かつxはXのサブクラスのインスタンスではない*4ということです。xはtmpに対するプロキシで、インスタンスのライフサイクルが違うのです。一つのtmpに対して複数回asやwithTraitで複数のプロキシを得ることもできるでしょう。

構文上の類似性があるので混同してしまうかもしれませんが、Scalaのnew時のtrait実装が無名クラス構文によるnewの拡張的な静的なものであるのに対して、Groovyの実行時trait実装はデコレータの生成であると言えます。つまり全然違います。traitの定番(?)の用途であろうDCIへの適用、つまりtraitをロールとして使用する際においては、Groovyの動作もできた方が親和性が高いとワシは思います。

Stringなどのfinalクラスにtraitを注入できる、という点も結果的な差異になります。まあ本当に注入したいのか、というのは置いておいて、ですが*5

なお、proxyから元のオブジェクトのproxyTargetというプロパティを取得できます(GROOVY-6692GROOVY-6695)。

落穂拾い

Groovyのtraitは、metaClass.mixinメソッドの上位互換*6的な代替であると見ることもできるかもしれません。metaClassは静的Groovy(@TypeChecked,@CompileStatic)配下では使えませんが、traitは使えるので、静的型チェックに親和性が高いバージョンのmixinと見ることができるかもしれません。さらに憶測ですが、traitの実行時実装の動作で奇妙にも思えるところは、metaClass.mixinのユースケースをカバーするためにそうなっているのかもしれません。

とりあえずおしまい。

*1:たぶん。Scalaは良く知らないので間違いがありましたらご指摘をお願いします。

*2:実際は思い切り見えるがw

*3:この機能は実はインターフェースに関してもともと従来のGroovyにある。「"String" as Runnable」とかやれる!!!。

*4:Groovy 2.3beta1,2では、xに対するメソッドコールはx的にmethodMissingなものについてはtmpに転送されます。そういうディスパッチを行なう「proxy」なのです。しかしXのインスタンスではなく代入互換ではない、さらにDGM非対応とか、mehodMissingなのでXよりもObjectのメソッドequals,hashCodeなどが優先されるとか、静的型チェックには対応できないとか、初学者には混乱を招き得る点がある気もする。

*5:DGMや拡張メソッド、metaClassによるメソッド注入では状態が単純には保持できないので意味あるかといえば意味はある。

*6:mixinはインスタンスを書き替えproxyを生成しないので厳密には上位互換ではない。

Java8のStreamでフィボナッチ数を計算する

フィボナッチ数ってあるじゃないですか。
Java8のStreamを使って書いてみます。Groovyで。

import static java.util.stream.Collectors.*
import java.util.stream.*

println Stream.iterate([1l, 1l]) {
    (old1, old2) = it
    [old1+old2, old1]
}.map{it[1]}.limit(10).collect(toList())

こんな感じですか。10個のフィボナッチ数を表示します。
無限ストリームなんかを作っちゃっています。

% groovy fib.groovy
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

調べると、streamてのは本質的にはIteratorフレームワークですな。
目的は並列処理うんぬんが主眼かもしれませんが、機構としてはIterator(Spliterator)のチェインをフルーエントなビルダーで作って、ケツのところでぶん回す、というものです。遅延リストとは違うし遅延評価とも関係ない*1。制約が多くてそれがおもしろい。別に記事書くかも。

*1:ごく広義の遅延評価とは言えるかもしれないが、広義すぎて何も言ってないのと同じmapやfilterなどで作る中間ストリームに与えたラムダ式が遅延実行される、という意味で遅延評価とは言える。それほど特別なシチュエーションではないと思うが、通常のforEachとかリースパターンと比べると遅延ではある。

Java 8のOptionalをGroovyから超簡潔に使用する

結論

Java8のOptionalは超すっきり扱えるよ、そう、Groovyならね。

Optionalって何?

Java 8で導入される新規クラスの一つ、java.util.Optionalは、メソッドの実行結果で成功する場合と失敗する場合があるときに、その返り値で成功と失敗を表現するためのものです。

  • Opitonalは単一要素を保持するコンテナ型。成功した場合は返り値をコンテナで保持させたものを返す。(成功時の返り値をラッピングする)
  • 「失敗」は固定のシングルトン(Optional.empty())として扱う

まあ、それだけの話といえばそれだけなのですが、効果は、

  • 失敗のある可能性のあるメソッドと無いメソッドをコメントではなくプログラムの一部として明示し、両者の違いをコーディング上も区別する
  • 失敗のある可能性のあるメソッドと無い混在・混同することのないようにコンパイル時チェックをできるようにする*1
  • 全体としては、nullで失敗値を表現する場合と比べ、NullPointerExceptionが発生しにくくなる

というところ。しかしながら、ラッピングされた値を取り出すにはOptional.get()を使うのですが、Optiona.empty()に対してget()を実行すると、NoSuchElementExceptionが発生します。なので、getを実行して値を取り出す場合には、Optional.isPresent()で、値がemptyではないことのチェックをする必要があります。でもそれって、nullチェックと同等なのであって、Optionalの目的からして悲しいものがあります。

なので、Optionalに関しては、値を取り出さずに操作を可能とするいくつかのメソッド(orElse,orElseGet,orElseThrow,map,flatMap, ifPresent, )を積極的に使うべきです。

問題は、これらのメソッド群が、単一のOptional値を操作するものでしかないということです。例えばそれぞれString型を保持する2つのOptional値があったとき、それを文字列結合したいとします。(この例はきしださんの記事より)。その場合以下のようにする必要があります。

Optional<String> fullName = (lastName.isPresent() && firstName.isPresent()) ? 
        Optional.of(String.join(" ", lastName.get(), firstName.get())) : 
        Optional.empty();

これは駄目な方のパターンですね。そのために、flatMapというものがあり、

Optional<String> fullName = 
  lastName.flatMap(ln -> 
  firstName.flatMap(fn -> 
    Optional.of(String.join(" ", ln, fn))));

こんな風に書けます*2。Optionalを使ったコーディングのキモは、「成功か失敗か」の個々値を組み合せた結果も「成功か失敗か」を正しく伝搬していることです。flatMap()はそれに関わってます(「複数のOptionalを組み合せた場合、どれか1つでも失敗したら全体は失敗」という意味を実装しているのはOptinalのflatMap())。
しかし、これらが、もともとやりたかったことは、

String fullName = String.join(" ", lastName, firstName);

だったわけですが、Optionalがちりばめられていて、たとえLambdaであろうとも、ちょっとごたごたしてますね。いやちょっとどころではない。

それ、Groovyで(ry

GroovyはJava 8に正式対応してるか不明ですが、試したbuild 1.8.0-ea-b115というバージョンでは動いているようです。

どうするかというと、Optionalに以下のようなメソッドを追加します。

Optional.metaClass.methodMissing = { String name, Object args ->
    assert args instanceof Object[]
    for (int i=0; i<args.size(); i++) {
        if (args[i] instanceof Optional) {
            if (args[i].isPresent()) {
                args[i] = args[i].get()
            }
            else {
                return Optional.empty()
            }
        }
    }
    if (delegate.isPresent()) {
        return Optional.ofNullable(delegate.get().invokeMethod(name, args))
    }
    else {
        return Optional.empty()
    }
}

ここではExpandoMetaclassを用いてますが、追加方法はuseでも拡張メソッド(ExtensionMethod)を含むモジュールとしてでも構わないでしょう。

すると、

Optional<String> fullName = lastName+" "+firstName

と書けます。つまりOptionalであることを意識せずに、通常の値と同様に透過的に操作でき、結果は結果をOptionalで包んだものになります。また、firstNameとlastNameのいずれかもしくは両方がOptional.empty()であるときには結果もOptional.empty()となります。

試してみます。

        Optional<Integer> firstName = Optional.of("FirstName")
        Optional<Integer> lastName = Optional.of("LastName")
        Optional NULL = Optional.empty()
        assert (lastName + " " + firstName).get() == "LastName FirstName"
        assert (NULL     + " " + firstName) == NULL
        assert (lastName + " " + NULL) == NULL
        assert (NULL     + " " + NULL) == NULL

全体コードはこちら

制約

もっとも制約もあって、Optionalに対する直接的なメソッド呼び出しを置き換えるだけなので、

  • Optional値を、他のメソッド(例えばString.join())などに渡すというケースには介入できない。上記でString.join()を使わずに+" "+しているのはこれが理由。+は左辺に対するplusメソッドの呼び出しとGroovyでは解釈されるので、それをOptionalのそれを置き換えることで実現されるからです。
  • 他にもあるかな?

という制約もあります。x.method(y,z,..)というパターンにおける失敗値の伝搬を解決するだけです。でもだいぶ可読性が高くなると思います。

Optional値を他のメソッド(例えばString.join())などに渡すというケースについては、以下のようにクロージャをOptionalに包んだ上でcallをmissingMethodでトラップしてクロージャに転送してもらえば良いですね。

assert (Optional.of{a,b -> String.join(' ', a, b) }("A", "B")) == Optional.of("A B")

完璧!

結論

Java8もGroovyで!
enjoy!

おまけ

Optionalをアプリカティブにする*3、ってのも考えましたが、結果は表記がGroovyとしてのメソッド呼び出しっぽくないので、いまいち。「&」がHaskellの<*>(ap)で%(mod)が<$>(fmap)に対応させています。全体はこちらに公開しておきます。

Optional<String> arg1 = Optional.of("Abc")
Optional<String> arg2 = Optional.of("Def")
Optional<String> NULL = Optional.empty()

assert ({a,b -> a+' '+b} % arg1 & arg2) == Optional.of("Abc Def")
assert ({a,b -> a+' '+b} % arg1 & NULL) == NULL
assert ({a,b -> a+' '+b} % NULL & arg1) == NULL

assert ({a,b -> String.join(' ', a, b)} % arg1 & arg2) == Optional.of("Abc Def")
assert ({a,b -> String.join(' ', a, b)} % arg1 & NULL) == NULL
assert ({a,b -> String.join(' ', a, c)} % NULL & arg1) == NULL

Closure c1 = {a,b -> a+b}
Closure c2 = {a,b -> a.toUpperCase()+b.toLowerCase()}
Closure c3 = {a,b -> a.length() * b.length()}

assert (c1 % arg1 & arg2) == Optional.of("AbcDef")
assert (c1 % arg1 & NULL) == NULL
assert (c1 % NULL & NULL) == NULL
assert (c2 % arg1 & arg2) == Optional.of("ABCdef")
assert (c2 % NULL & arg2) == NULL
assert (c2 % NULL & NULL) == NULL
assert (c3 % arg1 & arg2) == Optional.of(9)

汎用的といえば汎用的です。クロージャを介してですがString.joinも使えます。Haskellの<$>(fmap)に対応させている積りの%(mod)をClosureのメソッドに追加定義したかったところがExpandoMetaClassの制約により実現できず。できた*4

*1:現存するライブラリがすべてOptionalに対応していないことを考えると、この効果を得られるようになるのは控え目に言っても遠大だと言えるでしょう…。Java8のAPIのどれだけをOptional対応にするか、そして既存のnull返しライブラリを使わなくなるか、にもかかっています

*2:こう書けることにはモナド則とかが関わってるのですが、Optionalなら直感的には自明。

*3:Optionalはモナドであり、モナドはアプリカティブより強いのでこれは必ず可能

*4:[http://jira.codehaus.org/browse/GROOVY-3674]

Java8におけるindyとLambdaの絶妙な関係、もしくはSAMタイプを継承する内部クラスの.classファイルはどこへ行ったの?

JJUG CCC 2013 Springのさくらばさんのセッションで「Java 8の怖い話」として、Lambda式をJVM上で実行するにあたり、

  • invokedynamicを使っている
  • Lambda式の仕様は、意味的にはSamタイプを継承する内部クラスのインスタンスのはずだが、lambda式を使っているJavaソースをコンパイルしても、内部クラスに対応する.classファイルが生成されない

という話を聞きました。Lambdaの実行に、内部的にバイトコードを生成して使っている、という話だったのですが、indy使ってるってのは、良く考えると解せないです。Lambdaになんらかの動的な側面があるんでしょうかね???

ということで、調べてみました。

[:contents]

結論

結論から書くと、OracleのJava8のJDKのJavacは、Lambda式の本体を、SAMタイプを継承する内部クラスのメソッドではなく、コンパイル対象のクラスに直接所属するメソッドとしてコンパイルします。また、Lambda式の使用は、invokedynamic命令にコンパイルされます。そして確かに、コンパイル時には、そのメソッドを持つSAMタイプを継承するクラスの.classファイルは生成されません。

そして、invokedynamic命令の初回実行時に、その命令に付随させたブートストラップメソッドの処理の中で、SAMタイプを継承するクラスを動的にオンメモリにバイト列として生成し、それをクラスローダにロードさせ、そのクラスのインスタンスをnewする処理が、invokedynamicの実際の処理に置き換わります*1。この生成されたクラスのSAMメソッドではおそらく、MethodHandle経由で先の(2013/6/13削除。こちらを見ると、MH経由ではない)Lambda本体に相当するメソッドを呼び出します。

ちなみに、「オンメモリにクラスを動的に生成する」という処理を、Java VMではObjectweb asmライブラリを使って行なっています。JVMの中でasmを使ってるとは知らなかった。

indyをLambda式の評価に使ってる理由の一つは、おそらく、lambda式の初回呼び出し時までSAMタイプを継承したクラスの生成およびロードのタイミングを遅延させるためです。この方式なら、例えLambda式が何100個記述されていようとも、そのLambda式が実際に呼び出されなければクラスは全く生成もロードもされないので、クラスロード処理やクラスファイルを読み込むためのファイルアクセス処理が節約でき、起動や初期化の時間が短縮できるのでしょう。また、内部クラスという別クラスではなく、実際にそのクラスにLambda本体のメソッドがあるので、内部クラスを使った場合に生じていた可視性の問題の回避も簡略化できたはずです。

賢いわー。

(2013/06/12 23:52追記)
桜庭さんより(!)Facebookにてコメント頂き、本件に関し、この資料「Lambda: A peek under the hood」が参考になるとのこと。おお!! ざっと見ですが、利点として、字面が同じlambdaは生成SAMクラスを共有できる、とかもあるようです。Type profile pollutionは意味がわかりませんでしたorz。ありがとうございました>桜庭さん

確認してみよう

例えば、Lambda式を含む、

public class Test {
	static void foo(Runnable r) {
		r.run();
	}
	public static void main(String[] args) {
		foo(()->{System.out.println("hello");});
	}
}

こんなJavaソースをまずはコンパイルして実行してみます。

$ javac Test.java
$ java Test
hello
$ ls -la
total 16
drwxr-xr-x   4 uehaj  staff   136  6 12 22:22 ./
drwxr-xr-x  41 uehaj  staff  1394  6 12 22:20 ../
-rw-r--r--   1 uehaj  staff  1095  6 12 22:22 Test.class
-rw-r--r--   1 uehaj  staff   182  6 12 22:21 Test.java

確かに、Test.class以外のクラスファイルは生成されてませんね。javapしてみましょう。

$ javap -p Test
Compiled from "Test.java"
public class Test {
  public Test();
  static void foo(java.lang.Runnable);
  public static void main(java.lang.String[]);
  private static void lambda$0();   // ←★
}

★のところの「lambda$0();」がLamda式のボディに対応するメソッドです。

メソッドの宣言だけではなく、メソッド本体のバイトコードとかコンスタントプール情報も見るために、javapに-c -p -vオプションをつけて実行してみます。以降、行番号は判りやすさのために付与しました。

$ javap -p -c -v Test
     1	Classfile /Users/uehaj/work/201305/java8_2/y/Test.class
     2	  Last modified 2013/06/12; size 1095 bytes
     3	  MD5 checksum d69a213c626bb412c306b8f3ea88cac4
     4	  Compiled from "Test.java"
     5	public class Test
     6	  SourceFile: "Test.java"
     7	  InnerClasses:
     8	       public static final #56= #55 of #59; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
     9	  BootstrapMethods:
    10	    0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    11	      Method arguments:
    12	        #26 invokeinterface java/lang/Runnable.run:()V
    13	        #27 invokestatic Test.lambda$0:()V
    14	        #28 ()V
    15	  minor version: 0
    16	  major version: 52
    17	  flags: ACC_PUBLIC, ACC_SUPER
    18	Constant pool:
    19	   #1 = Methodref          #9.#21         //  java/lang/Object."<init>":()V
    20	   #2 = InterfaceMethodref #22.#23        //  java/lang/Runnable.run:()V
    21	   #3 = InvokeDynamic      #0:#29         //  #0:lambda$:()Ljava/lang/Runnable;
    22	   #4 = Methodref          #8.#30         //  Test.foo:(Ljava/lang/Runnable;)V
    23	   #5 = Fieldref           #31.#32        //  java/lang/System.out:Ljava/io/PrintStream;
    24	   #6 = String             #33            //  hello
    25	   #7 = Methodref          #34.#35        //  java/io/PrintStream.println:(Ljava/lang/String;)V
    26	   #8 = Class              #36            //  Test
    27	   #9 = Class              #37            //  java/lang/Object
    28	  #10 = Utf8               <init>
    29	  #11 = Utf8               ()V
    30	  #12 = Utf8               Code
    31	  #13 = Utf8               LineNumberTable
    32	  #14 = Utf8               foo
    33	  #15 = Utf8               (Ljava/lang/Runnable;)V
    34	  #16 = Utf8               main
    35	  #17 = Utf8               ([Ljava/lang/String;)V
    36	  #18 = Utf8               lambda$0
    37	  #19 = Utf8               SourceFile
    38	  #20 = Utf8               Test.java
    39	  #21 = NameAndType        #10:#11        //  "<init>":()V
    40	  #22 = Class              #38            //  java/lang/Runnable
    41	  #23 = NameAndType        #39:#11        //  run:()V
    42	  #24 = Utf8               BootstrapMethods
    43	  #25 = MethodHandle       #6:#40         //  invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    44	  #26 = MethodHandle       #9:#2          //  invokeinterface java/lang/Runnable.run:()V
    45	  #27 = MethodHandle       #6:#41         //  invokestatic Test.lambda$0:()V
    46	  #28 = MethodType         #11            //  ()V
    47	  #29 = NameAndType        #42:#43        //  lambda$:()Ljava/lang/Runnable;
    48	  #30 = NameAndType        #14:#15        //  foo:(Ljava/lang/Runnable;)V
    49	  #31 = Class              #44            //  java/lang/System
    50	  #32 = NameAndType        #45:#46        //  out:Ljava/io/PrintStream;
    51	  #33 = Utf8               hello
    52	  #34 = Class              #47            //  java/io/PrintStream
    53	  #35 = NameAndType        #48:#49        //  println:(Ljava/lang/String;)V
    54	  #36 = Utf8               Test
    55	  #37 = Utf8               java/lang/Object
    56	  #38 = Utf8               java/lang/Runnable
    57	  #39 = Utf8               run
    58	  #40 = Methodref          #50.#51        //  java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    59	  #41 = Methodref          #8.#52         //  Test.lambda$0:()V
    60	  #42 = Utf8               lambda$
    61	  #43 = Utf8               ()Ljava/lang/Runnable;
    62	  #44 = Utf8               java/lang/System
    63	  #45 = Utf8               out
    64	  #46 = Utf8               Ljava/io/PrintStream;
    65	  #47 = Utf8               java/io/PrintStream
    66	  #48 = Utf8               println
    67	  #49 = Utf8               (Ljava/lang/String;)V
    68	  #50 = Class              #53            //  java/lang/invoke/LambdaMetafactory
    69	  #51 = NameAndType        #54:#58        //  metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    70	  #52 = NameAndType        #18:#11        //  lambda$0:()V
    71	  #53 = Utf8               java/lang/invoke/LambdaMetafactory
    72	  #54 = Utf8               metaFactory
    73	  #55 = Class              #60            //  java/lang/invoke/MethodHandles$Lookup
    74	  #56 = Utf8               Lookup
    75	  #57 = Utf8               InnerClasses
    76	  #58 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    77	  #59 = Class              #61            //  java/lang/invoke/MethodHandles
    78	  #60 = Utf8               java/lang/invoke/MethodHandles$Lookup
    79	  #61 = Utf8               java/lang/invoke/MethodHandles
    80	{
    81	  public Test();
    82	    flags: ACC_PUBLIC
    83	    Code:
    84	      stack=1, locals=1, args_size=1
    85	         0: aload_0       
    86	         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
    87	         4: return        
    88	      LineNumberTable:
    89	        line 3: 0
    90	
    91	  static void foo(java.lang.Runnable);
    92	    flags: ACC_STATIC
    93	    Code:
    94	      stack=1, locals=1, args_size=1
    95	         0: aload_0       
    96	         1: invokeinterface #2,  1            // InterfaceMethod java/lang/Runnable.run:()V
    97	         6: return        
    98	      LineNumberTable:
    99	        line 5: 0
   100	        line 6: 6
   101	
   102	  public static void main(java.lang.String[]);
   103	    flags: ACC_PUBLIC, ACC_STATIC
   104	    Code:
   105	      stack=1, locals=1, args_size=1
   106	         0: invokedynamic #3,  0              // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable;
   107	         5: invokestatic  #4                  // Method foo:(Ljava/lang/Runnable;)V
   108	         8: return        
   109	      LineNumberTable:
   110	        line 8: 5
   111	        line 9: 8
   112	
   113	  private static void lambda$0();
   114	    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   115	    Code:
   116	      stack=2, locals=0, args_size=0
   117	         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   118	         3: ldc           #6                  // String hello
   119	         5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   120	         8: return        
   121	      LineNumberTable:
   122	        line 8: 0
   123	}

長いっ!

なので以降、分解して見ていきます。

Lambda式はどうなったか

Lambdaの本体に対応しそうなのは、

   113	  private static void lambda$0();
   114	    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   115	    Code:
   116	      stack=2, locals=0, args_size=0
   117	         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   118	         3: ldc           #6                  // String hello
   119	         5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   120	         8: return        
   121	      LineNumberTable:
   122	        line 8: 0
   123	}

の部分であり、確かにlambda$0メソッドが、

()->{ System.out.println("hello"); }

の「System.out.println("hello")」近辺に対応していそうです。

invokedynamicの動き

まず、mainメソッドに対応する以下の部分を見ると、

   102	  public static void main(java.lang.String[]);
   103	    flags: ACC_PUBLIC, ACC_STATIC
   104	    Code:
   105	      stack=1, locals=1, args_size=1
   106	         0: invokedynamic #3,  0              // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable;
   107	         5: invokestatic  #4                  // Method foo:(Ljava/lang/Runnable;)V
   108	         8: return        
   109	      LineNumberTable:
   110	        line 8: 5
   111	        line 9: 8

確かにinvokedynamic命令が生成されていることがわかります。このinvokedynamic命令に対応する「ブートストラップメソッド」は、javap出力の冒頭にある

     9	  BootstrapMethods:
    10	    0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    11	      Method arguments:
    12	        #26 invokeinterface java/lang/Runnable.run:()V
    13	        #27 invokestatic Test.lambda$0:()V
    14	        #28 ()V

ここのところですね。ブートストラップメソッドが表している実体は、静的メソッドへのポインタであり、ここでは具体的には、JDKの提供するAPIであるメソッドjava.lang.invoke.LambdaMetafactory.metaFactory()です。

LambdaMetaFactory

ブートストラップメソッドとして仕掛けられているLambdaMetaFactoryは、このケースでは最終的にjava.lang.invoke.InnerClassLambdaMetafactory.javaのspinInnerClass()あたりの以下の処理を呼び出します。

(略)
import jdk.internal.org.objectweb.asm.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;

(略)
    private final ClassWriter cw;                    // ASM class writer
(略)

    private Class<?> spinInnerClass() throws LambdaConversionException {
(略)    
        cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
                 lambdaClassName, null,
                 NAME_MAGIC_ACCESSOR_IMPL, interfaces);

        // Generate final fields to be filled in by constructor
        for (int i = 0; i < argTypes.length; i++) {
            FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argTypes[i].getDescriptor(),
                                            null, null);
            fv.visitEnd();
        }

        generateConstructor();

        MethodAnalyzer ma = new MethodAnalyzer();

        // Forward the SAM method
        if (ma.getSamMethod() == null) {
            throw new LambdaConversionException(String.format("Functional interface method not found: %s", samMethodType));
        } else {
            generateForwardingMethod(ma.getSamMethod(), false);
        }

        // Forward the bridges
        // @@@ The commented-out code is temporary, pending the VM's ability to bridge all methods on request
        // @@@ Once the VM can do fail-over, uncomment the !ma.wasDefaultMethodFound() test, and emit the appropriate
        // @@@ classfile attribute to request custom bridging.  See 8002092.
        if (!ma.getMethodsToBridge().isEmpty() /* && !ma.conflictFoundBetweenDefaultAndBridge() */ ) {
            for (Method m : ma.getMethodsToBridge()) {
                generateForwardingMethod(m, true);
            }
        }

        if (isSerializable) {
            generateWriteReplace();
        }

        cw.visitEnd();

        // Define the generated class in this VM.

        final byte[] classBytes = cw.toByteArray();

        ClassLoader loader = targetClass.getClassLoader(); // ★★
(略)
        return (Class<?>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length,
                                                                   loader, pd);
    }

asmのClassWriterで生成したバイト列classBytesを、★★以降でクラスローダでロードしているようです。
どういうクラスを生成しているか、までは実は追ってないのですが、MethodHandle経由で渡してきたlambda$0をinvokeするようなコードを含むメソッドを含むクラスが生成されるのではないかと思います(もしくはlambda$0のバイトコード本体をコピーする??まさかね)。(2013/6/13削除)
こちらを見ると、直接lambda$0メソッドをinvokevirtual/invokestaticで呼んでいるようです(2013/6/13追記)。

まとめ

APIリファレンスが日本語に訳されたので、invokedynamicあたりのAPI(MethodHandle, CallSite,....)を初めて読む気になりました(へたれ)。ありがとうオラクルの人(寺田さん?)。

invokedynamicは今さらながらおもしろいな。今、Brainfuckindyで実装してみるテスト中です。

*1:この置き換えはinvokedynamicの通常動作です。CallSiteのインストールの話ね。

オフラインどう書く第九回の問題をJava 8 Lambdaでやってみた。

という記事を書きましたが、今回はJava 8 Lambda式を使って書きなおしてみました。

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

class BusJava8 {
  
  int ceil10(int n) { return (int)(Math.ceil(n/10) * 10); }
  
  int half(int n) { return ceil10(n/2); }
  
  List parse(String input) {
    String[] tmp = input.split(":", 0);
    String p = tmp[0];
    String list = tmp[1];
    return Arrays.asList(Integer.parseInt(p), Arrays.asList(list.split(",", 0)));
  }
  
  int calc(int basePrice, List<String> passengers) {
    Map<String,Function<Integer,Integer>> map = new HashMap<>();
    map.put("An", (Integer it)->it);
    map.put("Ap", (Integer it)->0);
    map.put("Aw", (Integer it)->half(map.get("An").apply(it)));
    map.put("Cn", (Integer it)->half(map.get("An").apply(it)));
    map.put("Cp", (Integer it)->0);
    map.put("Cw", (Integer it)->half(map.get("Cn").apply(it)));
    map.put("In", (Integer it)->half(map.get("An").apply(it)));
    map.put("Ip", (Integer it)->0);
    map.put("Iw", (Integer it)->half(map.get("In").apply(it)));
    Map<String,List<List>> groups =passengers.stream()
      .map(it -> Arrays.asList(it, map.get(it).apply(basePrice)))
      .collect(Collectors.groupingBy(it -> (((String)it.get(0)).substring(0,1))));
    long size = (long)(groups.get("I").size()-groups.get("A").size()*2);
    groups.get("I").sort((a, b) -> (Integer)(a.get(1)) - (Integer)(b.get(1)));
    List<List> tmp = new ArrayList(groups.get("A"));
    tmp.addAll(groups.get("C"));
    tmp.addAll(groups.get("I").stream().limit(size).collect(Collectors.toList()));
    return tmp.stream().mapToInt(it->(Integer)it.get(1)).sum();
  }
  
  static void test(String input, String answer) {
    Java8 b = new Java8();
    List tmp = b.parse(input);
    int basePrice = (int)tmp.get(0);
    List<String> passengers = (List<String>)tmp.get(1);
    int expected = Integer.parseInt(answer);
    int result = b.calc(basePrice, passengers);
    assert result == expected;
  }

  public static void main(String[] args) {
      test("1480:In,An,In,In,In,Iw,Cp,Cw,In,Aw,In,In,Iw,Cn,Aw,Iw", "5920");
  }
}

上では、自分がJava8に全く不慣れなため、非効率・冗長な記述をしているところがあるかもしれません。また、そもそも元のクロージャを駆使したアルゴリズムがGroovy向きだった、など比較としてはフェアではないかもしれませんので、ご留意を。Java 8Lambdaの仕様も未確定だと思いますので、現時点でのeaでの仕様であることも注意ください。

で、感想を言うと、こりゃ書くのたいへんだわ、です。Java8の補完対応IDEを使わなかったこと、などもあると思いますが…。あと結構エラーメッセージが怖いです。たとえば

Java8.java:28: エラー: 不適合な型: 推論変数Rには、不適合な境界があります .collect(Collectors.groupingBy(it -> (((String)(it.get(0))).substring(0,1))));
^
等価制約: Map>>
上限: Map,Object
R,Tが型変数の場合:
メソッド collect(Collector)で宣言されているRはObjectを拡張します
インタフェース Streamで宣言されているTはObjectを拡張します
INT#1,INT#2がintersection型の場合:
INT#1はObject,Serializable,Comparableを拡張します
INT#2はObject,Serializable,Comparableを拡張します

とか。型指定を1つ間違えただけで上のメッセージは…。

他に、Groovyと比べると、例えLambdaを使っても、

  • オペレータオーバーロードが無い
  • Collectionとstreamが別ものである
  • リストやマップリテラルが無い
  • 破壊的ではないリストの結合とかsortとかが無い(見付けられなかっただけかも…)ようなので、メソッドチェインが途切れてしまうのも気になります

などにより、冗長性は高くなるようです。シンプルなケースを除き、高階関数をバリバリ使って関数型ライクに書くのは余り積極的になれない気がしました。リリースまでには多くが解決されてしまうかもしれませんが*1

Lambda式の良い点は、GroovyのCompileStticに比べても、型推論の精度が高いということ。GroovyのCompileStaticでは、クロージャの戻り値の型は推論に寄与してないようで、チェインすると2個目以降に明示指定が必要になります。

さて、速度はどうだったか、などはJJUG CCC 2013 Springのセッションにて(ステマ)。

*1:まあ、lambdaの主眼はマルチコア細粒度並列処理と言われているので、もしそうなら通常の処理にあえて使う必然性もないわけですけれども。