購読中です 読者をやめる 読者になる 読者になる

uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとか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:完全にかは不明。ターゲット型推論について要調査。

渋谷JVMで「いまさら始めようGroovy」を話しました

昨日、渋谷JVM

d-cube.connpass.com

にて発表させていただきました。

アテンドいただきましたコミュニティのみなさん、ビズリーチさま、聞いてくださったみなさん、ありがとうございました。また懇親会ごちそうさまでした(たこ焼たいへんおいしくいただきました!)。

他の言語の発表も聞くのも、またLTもたいへん楽しく参加させていただきました。名だたる皆さんと並んでの発表ということで、気おくれもし、準備とかパネルとか大丈夫かとか不安でしたが、サポートいただきなんとか乗り越えることができました。それとすばらしい会場ですねあそこはまた。起伏あり浜辺もあるし。

以下発表資料であります。

発表時に、会場のみなさんに「Groovy使っている人どのぐらいいますかー」と聞いたところ、非常に多くの割合*1で使われていたのでビックリし「およびでない、失礼しましたー」と帰りたくなりましたが、少しだけでも知見が増すところがあれば幸いなのですが、いかがなものでしたでしょうか。

かさねて御礼申しあげます。 以下、いただいたレスポンスなど

togetter.com shigemk2.hatenablog.com yukung.hatenablog.com takudo.github.io

*1:Gradle効果でしょうか。

TaPLのML実装をRustでやってみるシリーズ まとめ

型システム入門 −プログラミング言語と型の理論−、を読んでおります。

型システム入門 −プログラミング言語と型の理論−
Benjamin C. Pierce
オーム社
売り上げランキング: 336,081

これをちまちまと読みつつ、せっかくなので各章にある「ML実装」の演習課題をRustにポーティングしていこうと思っています。Rustの勉強と型システムの勉強が一石二鳥かと思ったら二重苦というかなんというか、実に楽しいっすね。Rustは初めてC言語にさわったときレベルで楽しい。

できた分は以下にコード逐次公開していきます。

関数型とかの論争について

身と蓋がナッシング

まず身も蓋もないことをいうと、結局はCPUの命令セットとして動く言語ですからして、大差はないんですよね。

でもこういうことを言うのは、「どうせ宇宙なんて原子の集合だろ!」という小学生なみです。 なので言わない。でもその認識を捨ててもならない。魔法はないです。地続きではあるのです。

「命令型」か「関数型*1」か

さてその上で、問うべき問いが、「命令型」か「関数型」であるならば、関数型に明確な優位性があらわれます。イミュータブルデータ。いい響きですね。map,filter,...いいですね。for禁止、シビれます。いいか、おまえら! forは禁止だ! 了解しました軍曹殿!

この利点はあきらかで、かつ入手することが比較的容易です。scalaでもJava8でもreactでも、それなりに努力すればそれなりのものが手に入りそうです。

このレベルではOOPとの対立項はないんじゃないかと思います。併用して両方よいとこ取りすればよいでしょう。今そこにある利点。うれしいうれしい。「副作用はほどほどにね!」てなもんです。

(追記)プログラム構成方法論としてOOP vs. FP

関数型とOOPの比較の観点として、たとえばプログラムの構造がクラスの集合として構成されるのか、関数なのか、制御フローがオブジェクトに対するメソッド呼び出しやメッセージパッシングとして構成されるのか、関数や高階関数呼び出しでなのか、という構成方法の問題として語られることもあります。本論では、ここにはあまり主張はなくて、少なくとも極端なのは良くないでしょうよと。原理主義者による信仰化はおひきとりねがいたいし*2オブジェクト指向養成ギブスとかの冗談を真にうけるのもヤメレです。

相互のエミュレーションたとえばScalazとか、HaskellのLensとかObjectiveみたいのもあることだし(理解してないけど)、結局は程度問題に見える。OOPとFPの差と、同じ関数型なら関数型同士の差(正格、非正格はともかく、純粋、非純粋の差は小さくない*3 )、OOP言語の派閥間の差(e.g. Smalltalk vs. C++)を比べると、どっちが大きいかわからなくなりそうだし、Rustみたいにどっちだかわからん言語もおるし。

優劣の問題ではなく、それぞれのスタイルの利点と欠点をちゃんと把握した上でなら、あとは好みも混じえて、解こうとする問題を、適切な労力でとける方を選べばいいじゃんでしょう。

「純粋」関数型の意義

ここらへんから、行方があやしくなってきます。関数型は良いとして、副作用を禁止までして、「純粋」にまでする必要があるのか? モのつくあれが登場してきそうです。

ブコメで純粋関数型は「状態の扱い方が普及のネックになる」旨の指摘を頂きましたが、それはその通りですね。ちなみに、実用レベルの純粋関数型言語が一生懸命にやっているのは、状態を純粋関数から分離し管理するということです。状態を「扱う」ことについては、ある意味非純粋関数以上に意識的に「扱う」のです。状態が、野放しに、プログラムの全体に渡ってばらばらに広がっているのか、そうじゃなくて言語のレベルで、言語機能や型システムのサポートのもとで例外無く強制的に管理するか、という違いです。この状態管理の強制が、純粋関数型を知らない人にとっては、敷居になり得るので、それがいかにわかりやすく普及啓蒙されるか。そのためには、つい出てくる圏論・数学・論理学用語とかがネックになり得るし、コミュニティの文化風土の問題(モヒカン問題)でもあるのかもしれません。ただ、もっと重要なのは、概念が本当に整理され、過剰な一般性や抽象化がなされすぎてはいないか、用語がわかりやすいものになっているかです。今まではそこは全然駄目で、今後、純粋関数型言語が普及していくためには、そこも本気で問われていかねばなりません。

静的でリッチな型システム

「関数型」とは直交し得るので、これが論点なのかすらについても議論があるかもしれないのが、強力な静的型システムをどう評価するのかです。多相型はいいとして、ヒンドリーは見るでしょうか見るなということでしょうか。存在型は、全称量化は、高階多相は、依存型は、リージョン型とか線形型とか幽霊型とかカリーハワード対応とか型レベルプログラミングとかどうなんでしょうか。これらは頭の良いひとたちのタワゴトなんでしょうか。正直、難しすぎやしませんかね。業務で使うとか、どうなんでしょう。我々の目の黒いうちにどうにかなる気がしませんよ。

何を未来に見据えるか?

ポイントは、現在のプログラミング言語の比較の問題ではなく、将来への流れなんだと思ってます。思うに、30年後のプログラミングというのは、極めてリッチな計算資源の元に以下が可能になっているはずです(願望)。

「 効果」「範囲」の明確な解析とそれに元付いたデバッグ、合成、分解、コンピュータによる強い支援を受けたプログラミング

たとえば、プログラムの実行結果として、Yを期待していたのにXという結果が得られたとして、その計算過程の何がYとXの差を生んだのか? 当初Xという結果だったのに、Yという結果になってしまったとき、その間に加えた変更a,b,cのどの操作がその違いを発生させたのか? aだけを適用したら、bだけを適用したら、aとcを適用したら、という順列組合せを実際に瞬時実行し、その結果を産む原因の候補としてのソースコードの変更点が瞬時にリストアップされたらどうでしょう。未来っつーぐらいだら、そうなるべきですよね。ソースの変更は、逐次意味の単位でアトミックに記録され、巻き戻したり、取捨選択して組み合わせたりが、自在にできます。gitのもっと進んだ姿。

あるいはソースを修正後に、再実行なんかはしません。個々のソースの「影響」は、分離されて記録管理されているので、特定箇所の修正は、その範囲が生むであろう「効果」がgitのfast forwardのように再適用されて、更新された実行結果を再表示するだけです*4。もちろん並列処理でのタイミングに基づく非決定性もあってはなりません。

このためには、命令型言語のように副作用を野放しにはできず、非純粋な言語でも駄目です。純粋関数型言語の延長で副作用を明示的管理しなければなりません。また、非決定性の排除が担保できたり、個々の操作の適用を分離して操作可能なのは、強い静的型の元でしかありません。合成や分割などの機械的操作の保証を得られるのも、リッチな型の元でしかありえません。数学的基盤も十全に必要でしょう。

その結果として、初めて、人間は、今は考えられないほど強力なコンピュータの支援の元で、CPUが4個とか16個じゃなく、数千数百万倍のコンピュータパワーが1人の人間がプログラミングする際に独占できるような状況下(そんなことは未来では当然できます)で、deep learningやビッグデータなんかも駆使して、より迅速に、より正しいプログラムを書けるようになるでしょう。

そのような状況に繋がる方向性は、純粋関数型や強いリッチな静的型の延長でしか有り得ません。現在は過渡期としてのそれが存在しているだけなのでその利点が十分には見えていないだけです。

まとめ

まとめると、強いリッチな静的型と結合した純粋関数型プログラミングがより重要な理由は、それが未来だからです。それが未来な理由は、コンピュータからの支援を受けるしか知的作業としてのプログラミングに未来はなく、コンピュータから支援を受ける道を開くのは、意図をより形にあらわせる静的型システムであり、分解結合が容易で副作用を客体化して扱える純粋関数型でしか有りえないからです。「名前重要」とか言ってられるのは相手が人間のときだけです。

目の黒いうちになんとかならないかなー。

*1:本エントリでは、関数型を「関数型プログミング」もしくは「関数型プログラミングスタイル」の意味であいまいに用います。命令型は命令型プログラミング

*2:じゃあ「すべてはクラス」「すべてがオブジェクト」というのがすでに信仰かどうか?ですが、まあ近いとだけ。そんなら「すべてが関数」は信仰か? うーん。うーん。ラムダこりゃ…。

*3:一方で、Haskellも*MLも同じMLファミリと考えることもできる。Erlangはしらない。

*4:こっそり追記するのもあれですが、これは驚異のタイムトラベリングデバッガ、elm-reactorがすでに実装している動作、FRPベースのリロード更新動作、を元に想像しています。

G*Magazine Vol.8に記事をかきました

G*Magazine Vol.8に、Groovy 2.3, Groovy 2.4β4までで導入された新機能の解説記事を書きました。

http://grails.jp/g_mag_jp/images/gmagjp_8.png

PDF版もありますが、ブラウザで見れる以下のリンクを紹介します。

出稿タイミングの都合で、2.4最新版には対応しておりませんので悪しからず。

G*なWeb API

Grails3もカウントダウンな感じのおり、イベント紹介です。以下引用。

G*ワークショップZは日本Grails/Groovyユーザーグループの定例イベントです。Java仮想マシン上で動作するGroovyGrailsGradleSpockvert.xといったG*技術をテーマに、ハンズオンやコードリーディングなど参加型の内容を金曜日に開催しています。

今回のG*ワークショップ"Z" 第19弾は、「G*なWeb API」と題して、昨今話題のWeb APIに関連した以下の内容です。いずれもハンズオン形式ではありません

Grails 3でWeb APIを簡単に作ろう!

Grails 3の新機能の紹介を交えながら、GrailsでのWeb API構築がいかに容易にできるかをお話します。GrailsのREST機能と、Web Microプロファイルなどを含めてご紹介する予定です。

REAL Objects : 動的なエンタプライズAPIアーキテクチャをどう構築するか

REAL Objectsは、(例えばSmalltalkのような) メッセージ・ベースのオブジェクト指向アーキテクチャを現代の並列/分散環境で実現するものです。 現実の業務システムのような、大規模で、ドメイン・モデルが動的/多様で, 非パブリックなAPIを持つアーキテクチャをどう実現するかについて、 G*技術を (多少) 絡めつつ, お話しします (ワークショップ形式ではありません)。

講演者紹介

こちらからお申し込みください。

G*なWeb API - 日本Grails/Groovyユーザーグループ | Doorkeeper

Elmでやってみるシリーズ16: マウスストーカーを実装する

リアクティブプログラミングの技術を用いてマウスストーカーを実装する - はこべブログ ♨」という記事があり、興味深いのでElmのリアクティブプログラミングで似たようなことをやってみました。

全画面表示はこちらから。

コードは以下で、プロジェクト全体はこちらにあります。

import Text
import Window
import Time
import Mouse
import List
import Signal
import Graphics.Element(..)
import Graphics.Collage(..)
import Color(..)
import Signal(Signal,(<~),(~))
import AnimationFrame

-- マウスの座標をCollageの座標に変換するいつもの関数
mousePos : Int -> Int -> Int -> Int -> (Float, Float)
mousePos x y w h = (toFloat x-(toFloat w/2)
                   , -(toFloat y)+(toFloat h/2))

-- ★の表示
star : Int-> Int -> (Int, Int) -> Form
star w h (x, y) = Text.fromString "★"
                        |> Text.color orange
                        |> Text.centered
                        |> toForm
                        |> move (mousePos x y w h)

-- ビューの定義
view : List (Int, Int) -> (Int, Int) -> Element
view posList (w,h) = collage w h (List.map (star w h) posList)

-- ★の座標のリストのSignalを作る
stars : Signal(List (Int, Int))
stars = let 
          trace = Time.delay 100 -- 100ms遅延を与えたSignalを生成
          p1 = Signal.sampleOn AnimationFrame.frame Mouse.position -- 最初はマウス座標を追う
          p2 = trace p1 -- 以降、一個前の座標を追うようにする
          p3 = trace p2
          p4 = trace p3
          p5 = trace p4
          p6 = trace p5
          p7 = trace p6
          p8 = trace p7
          p9 = trace p8
          p10 = trace p9
          p11 = trace p10
          p12 = trace p11
          p13 = trace p12
          p14 = trace p13
        in (\a b c d e f g h i j k l m n -> [a, b, c, d, e, f, g, h, i, j, k, l, m, n])
         <~ p1 ~ p2 ~ p3 ~ p4 ~ p5 ~ p6 ~ p7 ~ p8 ~ p9 ~ p10 ~ p11 ~ p12 ~ p13 ~ p14

-- 表示関数viewをliftして★の座標をあてがう
main : Signal Element
main = view <~ stars ~ Window.dimensions

非常に簡潔です。

説明と注意点など

  • Mouse.positionおよびTime.delayが、シグナル(beacon.jsでいうEventStreamに相当)を生成しています。ちなみに次期Elm 0.15では、Signalは名称変更(Stream/Varyingに分割)が検討されているようです
  • 上記コードではマウス位置のサンプリング間隔を効率化するために AnimationFrameというコミュニティパッケージを使用しています。なので他のパッケージを使用する機能がないtry-elmshare-elmでは実行できません。sampleOnをfps 60とかですれば、もしくはsampleOnを使用せず直接Mouse.positionを使用すれば、AnimationFrameへの依存は除去できます。
  • 制限としては、ElmのCanvasベースのAPIであるGraphics.Collageを用いているため、★がifameの枠外まで追随することはありません。Nativeやportを用いてJSと連携すれば、真のマウスストーカーができるかもしれませんが試してはおりません。ちなみに次期版0.15ではJSとの連携能力が大きく変わるようです。
  • 上記コードでp1〜p14を列挙していますが、例えばmapを使用してリストを生成していないのは、Signalは動的に生成できないからです。Elmでは、すべてのSignalの値の依存関係に対応する「Signalグラフ」というものがコンパイル時に静的に決定されます。そのこともあり、不定件数の「Signalのリスト(List Signal a)」というものは作成できたとしても、(Signal (List a))に変換できないのです。sequence :: [m a] -> m [a] が欲しいところですが、そういう関数はなく、定義することも型制約*1で意図的に禁止されています。

余談

もうすぐ出るらしいElm 0.15は、非常に楽しみです。

参考リンク

Elmシリーズの他のエントリはこちら

*1:Signalはアプリカティブでモナドではない。