petitviolet blog

    気持ちよくAndroidを書くために

    2015-12-03

    QiitaJavaAndroid

    ちょっとでも Java7 な Android を気持ちよくプログラミングしたい、 Java っぽい(?)ところがありつつもモダンなパラダイムが取り入れられている Scala が羨ましく見えたので、Scala っぽく書きたいという意志から書いています サンプルコードでは紙面の都合上、retrolambda で lambda 式使っています

    Scala の羨ましいところ

    挙げればキリが無いですが、以下とします

    • 関数型プログラミング
    • 強力な式

    関数型プログラミングっぽく

    これについてはRoppongi.aar #2 - connpassで私が発表した内容がベースとなっています

    OO な java で無理して関数型プログラミングを目指すのではなく、関数型プログラミングの中でもモナドといわれる便利なデータ構造を便利に使いたい、といった程度のものです

    そこで、Android でもモナド実装して使えば気持ちいいんじゃないか、ということで簡単なライブラリにしました petitviolet/Android-Monad なおモナド則を満たしているかのチェックは行っていませんが、その点はご了承下さい

    RxJava を使えば関数型プログラミングっぽく書くことも出来ますが、ここでは話しません

    使い方

    maven にアップロードしてあるのでこれで利用できます

    build.gradle
    compile 'net.petitviolet.android:monad:0.5.0'
    

    いまサポートしているのは以下です

    • Maybe
    • ListM
    • State

    Maybe

    いわゆる Maybe モナドで、null セーフなプログラミングを実現するためのものです Maybeモナドのいいところとして、中身がnullであってもそうでなくても全く同じように一連の処理を適用した結果としてJustNoneが得られるため、@nullableな値に対するボイラープレート的 null チェックを無くすことが出来る点が挙げられます

    Maybe.ofのファクトリーメソッドで初期化して、それに対して便利メソッドが生えているので、メソッドチェーンで処理を続けていくのが想定される使い方です 例えば DB へのMaybeな select を行い、ヒットするものがあった場合のみ処理を続行する、というの綺麗に書けたりします

    Maybe<Integer> maybeInt = Maybe.of(x);
    maybeInt.flatMap(i -> findById(i))  // Maybe<Data> findById(int n)
            .map(Data::getCost)  // int getCost()
            .filter(j -> j > 300)
            .foreach(k -> Log.d(TAG, "result: " + k));
    

    @nullableなものはとりあえずMaybeでくるんでしまえば安全に操作することが出来るようになります

    ListM

    リストモナドですが、Scala っぽいコレクション操作が出来るようになっています

    ListM<Integer> listM = ListM.unit();
    listM.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    ListM<String> result = listM.filter(i -> i % 3 == 0)
            .flatMap(i -> ListM.<Integer>of(i * 10))
            .map(s -> s + "!");
            .foldRight((a, acc) -> a + ", " + acc, " <= end");
    // result -> 90!, 60!, 30!, <= end
    

    Listインタフェースを implement してあるので、便利なListとして使えるイメージです

    Android っぽい例として以下のようなものがあります ViewGroupの中からEditTextfindViewByIdしてきて入力が空でないものについて、Integerに変換して合計値を求める、といった処理がさらっと書けるようになります

    int sumOfInput = ListM.of(viewGroup1, viewGroup2,...)
        .map(l -> ((EditText) l.findViewById(R.id.input)))
        .map(editText -> editText.getText().toString())
        .filterNot(s -> TextUtils.isEmpty(s))
        .map(Integer::parseInt)
        .foldLeft(0, (acc, i) -> acc + i);
    

    State

    State モナドです 一連の処理の中で状態変数を共有することが出来るものとなっています State 自体は状態を使った処理を記述するもので、applyに初期状態を渡すことで処理が走って結果が得られるようになっています 一応作った程度なので例がちょっと微妙ですが、以下のように使えます

    Tuple<Integer, List<String>> result = State.<List<String>, Integer>unit(1)
            .map(i -> i + 10)
            .map(i -> i * 2)
            .flatMap(integer -> State.init(new Function.F1<List<String>, Tuple<Integer, List<String>>>() {
                @Override
                public Tuple<Integer, List<String>> invoke(List<String> strings) {
                    if (integer % 2 == 0) {
                        strings.add("awesome");
                        return Tuple.of(integer, strings);
                    } else {
                        strings.add("Oops");
                        return Tuple.of(-100, strings);
                    }
                }
            })).apply(new ArrayList<>());
    // result._1 == 22;
    // result._2 == ["awesome"];
    

    強力な式

    scala には java よりも強力で便利な式がいくつかあります それを無理やり java でもそれっぽく使えるようにしたライブラリを作りました petitviolet/Android-scaex

    使い方

    同じく maven にアップロードしてあるので dependencies に追加すれば使えます

    dependencies {
        compile 'net.petitviolet.android:scaex:0.1.1'
    }
    

    if 式

    java のifは文ですが、scala のifは式です そもそも値を返せる点で大きく異なります ifの条件分岐によって値を得たい場合、java なら以下のように書きます

    int target = //...;
    String sentence;
    if (target > 10) {
        sentence = "large";
    } else if (target > 5) {
        sentence = "medium";
    } else {
        sentence = "small";
    }
    

    if-else だと三項演算子で簡潔に書けますが、条件が複雑になれば見辛くなり、else if を使いたい場合だとそもそも使えなかったりします

    そこで、IFを使うと以下のように書けます

    String sentence = IF.<String>x(target > 10).then("large")
             .ElseIf(x > 5).then("medium")
             .Else("small");
    

    単純に行数が減って多少見やすくなりました ジェネリクスとしてStringを渡すのが冗長でやや不満が残る点ですね 渡さないとObjectになってしまうので、現状はやむを得ない対応となっています

    またthenには引数を 0 個受け取って処理を実行して値を返すFunction.F0を渡せるようにしてあるため、複雑な処理を行うことも出来ます

    String sentence = IF.<String>x(x > 10).then("large")
            .ElseIf(x > 5).then(() -> {
                Log.d(TAG, "nice if-expression");
                return "medium";
            })
            .Else("small");
    

    pattern match

    関数型プログラミングとも紐づく特徴ですが、scala には pattern-match(match-case)が実装されています これと同じことを java でするには残念ながら if 文しかないわけですが、Matchを使うとちょっとそれっぽく書けます

    abstract class Hoge {
        abstract public String call();
    }
    
    class Foo extends Hoge {
        public String call() {
            return "foo";
        }
    }
    
    class Bar extends Hoge {
        public String call() {
            return "bar";
        }
        public String shout(){
            return "yeah!";
        }
    }
    
    private void some(Hoge hoge) {
        Match.<String, Hoge>x(hoge)
                .Case(Foo.class).then(Hoge::call)
                .Case(Bar.class).then(h -> ((Bar)h).shout())
                .eval("Not Matched!");
    }
    

    CaseFoo.classと class オブジェクトを渡していたり cast しているのがわりと微妙ですが、何となく match-case っぽいことを実現できています Caseの中に class オブジェクトを渡せばパターンマッチのようにその型チェックを実行し、booleanを渡せば guard として機能します evalにはデフォルト値としてマッチしなかった場合の返り値を渡すことも出来ます。

    まとめ

    モナドと pattern-match 作ったのに、do 式(for 式)が無くて片手落ち感はんぱないですね 自作ライブラリの宣伝みたいになってしまいましたが、関数型プログラミングを java で実現するためのライブラリとかは他にもあります 例) Functional Java

    現状は scala 書いているとその表現力によって簡潔に書けたりするのが、java だから...と諦めて冗長に書かざるを得ない状況が辛いので、ちょっとでも簡潔に綺麗に書けるようになれば幸いです とにかく早く java8 来てくれ、という思いです

    from: https://qiita.com/petitviolet/items/531ce42775333644ea49