blog.petitviolet.net

気持ちよく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