54
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AndroidAdvent Calendar 2015

Day 3

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

Last updated at Posted at 2015-12-03

ちょっとでも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来てくれ、という思いです

54
52
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?