3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

なぜKotlinにもOption<A>が必要なのか

Posted at

簡潔なタイトルにしたかったのでいろいろ情報が足りていない。順を追って説明していく。

ここでの前提として、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ではSomeNoneというサブタイプを用いる。値が取得できれば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の場合、firstOrElsenullを返す`(){
        assertEquals(null, listOf(null, 2, 3).firstOrElse { -1 })
    }

image.png

そう、この場合テストは成功しない。これがいわゆるネストされた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の場合、firstOrElsenullを返す`(){
        assertEquals(null, listOf(null, 2, 3).firstOrElse { -1 })
    }

あとは各種Option関係の関数の使い方を覚えれば良い。といっても関数型プログラミングを知っていれば出てくるアイツらだ。isSome, isNone, getOrNull, getOrElseである。

おわりに

ArrowはKotlinで関数型プログラミングをする上で必須というか、事実上標準の地位を固めつつある。JetBrainsの公式ブログでもKtor×Arrowの事例を紹介してるくらいなので、ここから先Arrowも前提知識としてある程度知っておくほうがいいのかもしれない

3
3
0

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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?