簡潔なタイトルにしたかったのでいろいろ情報が足りていない。順を追って説明していく。
ここでの前提として、Kotlinで関数型プログラミングをしようとしている。
Option<A>
とは
関数型プログラミングでしばしば使われる型のことだ。Option<A>
の存在意義を語るには必然的にnullについて触れる必要がある。関数型プログラミングの思考を突き詰めれば純粋関数を常に作る・扱うことに行き着く。ひとまず純粋関数の定義を復習する。
- 戻り値は常に一つ
- 関数は引数にのみ基づいて戻り値を計算する
- 関数は既存の値を変更しない
では一つ問おう。このScalaのコードは嘘をついているか?
def extractYear(date: String): String = {
if (date != null && date.matches("\\d{4}-\\d{2}-\\d{2}")) date.substring(0, 4)
else null
}
答えはYesである。この関数はStringを返すと言っていながら、実はnull
を返す可能性もある。では嘘をつかせないためにどうするかというと、関数型プログラミングでは往々にしてOption
という型を使う。上記のコードをOption
型を使って直すとこうなる。
def extractYear(date: String): Option[String] = {
if (date == null || !date.matches("\\d{4}-\\d{2}-\\d{2}")) {
None
} else {
Some(date.substring(0, 4))
}
}
OptionではSome
とNone
というサブタイプを用いる。値が取得できればSome
を。取得できなければNone
という値を返す。この状態であればシグネチャは嘘をついていない。
Kotlinにおけるnull
さて、ここまでの話を聞いてKotlinを知っている人であれば「???」となっているだろう。それもそのはず。KotlinではOption
などと長々と書かずとも?と書けばそれで「null
を返すかもよ」と表現できるからだ。これがJavaやScalaにはないKotlinの強みと言って良い。
fun extractYear(date: String?): String? {
return if (date != null && date.matches(Regex("\\d{4}-\\d{2}-\\d{2}"))) {
date.substring(0, 4)
} else {
null
}
}
ネストされたnull許容性の問題
以上から、「KotlinではOption
なんてものは必要ない。便利だね!」 …という話になると思っただろうか?残念ながらそうはならないのだ。結論から言うとKotlinにおいてもOption
が必要になる場面がある。
さて、以下のようなコードがある。このテストは成功するだろうか?
fun <A> List<A>.firstOrElse(default: () -> A): A = firstOrNull() ?: default()
@Test
fun `リストの先頭がnullの場合、firstOrElseはnullを返す`(){
assertEquals(null, listOf(null, 2, 3).firstOrElse { -1 })
}
そう、この場合テストは成功しない。これがいわゆるネストされたnull許容性の問題である。
問題を解決するための道具: Arrow
では以上の問題を解決するにはどうするか。要はリスト自体が存在すればその一番目の要素をSome
。リストがnull
ならばNone
と扱えればよいのだ。そして一番目の要素がnull
ならば、それはSome(null)
として扱うのである。しかし、これを実現する道具はKotlin本体に標準で備わっていない。よってArrow
というライブラリを使う。
これを使うことで以下のような表現が可能になり、テストが通るようになる。
fun <A> List<A>.firstOrElse(default: () -> A): A =
when(val option = firstOrNone()) {
is Some -> option.value
None -> default()
}
@Test
fun `リストの先頭がnullの場合、firstOrElseはnullを返す`(){
assertEquals(null, listOf(null, 2, 3).firstOrElse { -1 })
}
あとは各種Option関係の関数の使い方を覚えれば良い。といっても関数型プログラミングを知っていれば出てくるアイツらだ。isSome
, isNone
, getOrNull
, getOrElse
である。
おわりに
ArrowはKotlinで関数型プログラミングをする上で必須というか、事実上標準の地位を固めつつある。JetBrainsの公式ブログでもKtor×Arrowの事例を紹介してるくらいなので、ここから先Arrowも前提知識としてある程度知っておくほうがいいのかもしれない