気持ちよく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 にアップロードしてあるのでこれで利用できます
compile 'net.petitviolet.android:monad:0.5.0'
いまサポートしているのは以下です
- Maybe
- ListM
- State
Maybe
いわゆる Maybe モナドで、null セーフなプログラミングを実現するためのものです
Maybe
モナドのいいところとして、中身がnull
であってもそうでなくても全く同じように一連の処理を適用した結果としてJust
かNone
が得られるため、@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
の中からEditText
をfindViewById
してきて入力が空でないものについて、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!");
}
Case
にFoo.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