uehaj's blog

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

JCUnitのBuilder APIをためす

以前、以下の記事で紹介した、JCUnitの開発がすすんでいます。(開発ブログ, ソース)

uehaj.hatenablog.com

主な拡張としては、状態機械をモデリングしてテスト生成ビルダーAPIの整備などなど*1

Spockからの利用サンプルをかきなおしてみました。ビルダーAPIを使用しています。

@Grab('com.github.dakusui:jcunit:0.5.4')
@Grab('org.spockframework:spock-core:1.0-groovy-2.4')
import com.github.dakusui.jcunit.core.factor.Factors
import com.github.dakusui.jcunit.core.factor.Factor
import com.github.dakusui.jcunit.generators.TupleGenerator
import com.github.dakusui.jcunit.core.FactorField;
import com.github.dakusui.jcunit.core.tuples.Tuple;
import com.github.dakusui.jcunit.constraint.constraintmanagers.ConstraintManagerBase;
import com.github.dakusui.jcunit.constraint.ConstraintManager;
import com.github.dakusui.jcunit.exceptions.UndefinedSymbol;
import spock.lang.*

class HelloSpec extends Specification {

    static ConstraintManager closureConstraintManager(names, Closure clos) {
        return new ConstraintManagerBase() {
            @Override
            boolean check(Tuple tuple) throws UndefinedSymbol {
                Binding binding = new Binding()
                names.each {
                     if (!tuple.containsKey(it)) {
                         throw new UndefinedSymbol(it)
                     }
                     binding.setProperty(it, tuple[it])
                }
                clos.delegate = binding
                clos.call()
            }
        }
    }

    static Collection genPairwiseTestData(Map factors, Closure constraint = null) {
        def builder = new TupleGenerator.Builder()
        if (constraint != null) {
            builder = builder.setConstraintManager(closureConstraintManager(factors.keySet(), constraint))
        }
        builder.setFactors(new Factors(factors.collect{k,v -> new Factor(k, v)}))
        .build()
        .collect{ it.values() };
    }

    static intLevels =  [ 1, 0, -1, 100, -100, Integer.MAX_VALUE, Integer.MIN_VALUE ];
    static stringLevels = [
        "Hello world", "こんにちは世界", "1234567890", "ABCDEFGHIJKLMKNOPQRSTUVWXYZ",
        "abcdefghijklmnopqrstuvwxyz", "`-=~!@#\$%^&*()_+[]\\{}|;':\",./<>?", " ",
        "" ]

    @Unroll
    def "分配法則(a=#a,b=#b,c=#c)"() {
    expect:
        c*(a+b) == c*a + c*b
    where:
    [a,b,c] << genPairwiseTestData([a:intLevels,
                                    b:intLevels,
                                    c:intLevels])
    }

    @Unroll
    def test1() {
    expect:
        c*(a+b) == c*a + c*b
    where:
    [a,b,c] << genPairwiseTestData([a:intLevels,
                                    b:intLevels,
                                    c:intLevels], { return a > 0 && b != c })
    }

    @Unroll
    def test2() {
    expect:
        (a+b).size() == a.size() + b.size()
    where:
    [a,b] << genPairwiseTestData([a:stringLevels,
                                  b:stringLevels])
    }

    @Unroll
    def test3() {
    expect:
        a+b == b+a
    where:
    [a,b] << genPairwiseTestData([a:[1,2,3],
                                  b:intLevels])
    }

    @Unroll
    def test4() {
    expect:
        a == b || a != b
    where:
    [a,b] << genPairwiseTestData([a:MyBoolean.values() as List, b:MyBoolean.values() as List])
    }

}

enum MyBoolean {
    True,
    False
}

以前のようにアノテーションを使用せずとも使用できるようになりました。

気付いた点としては、

  • 各型のレベルのデフォルト値をAPIクラスからうまく参照できなかったので自前で定義しています。 (上記の static intLevels = [ 1, 0, -1, 100, -100, Integer.MAX_VALUE, Integer.MIN_VALUE ]の部分)
  • SpockのデータパイプAPIにもうちょっと柔軟性が欲しい。今の呼び出し方だとデータ指向テストの疑似変数名の指定に重複があるので、疑似変数名を取得もしくは設定できるAPIがあると良いのだが。Spock Extensionで触れるフックがあるかな。

など。楽しいです。

*1:他に、生成テストデータの保存、再生などもなされているようです。

今こそッ、始めようGrailsブートキャンプ!!!!

以下のイベントが予定されています。 Grails3対応のGrailsブートキャンプです。

Grails3というのが出たタイミングで、ちょっと取り組み直しみよう、という向きに最適です。

jggug.doorkeeper.jp

山本さん(id:yamkazu)NTTSOFTが講師です。

ご興味があればぜひご検討を。

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最新版には対応しておりませんので悪しからず。