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