弊社プロダクト「PHONE APPLI PEOPLE (旧:連絡とれるくん)」では、サーバサイドに Kotlin を採用しています。
今回は、初見だとたぶん使い道が分かり辛い Nothing
型について紹介します。
Nothing 型とは
Kotlin language specification
言語仕様によると、ユーザが定義した型も含む、すべての型のサブタイプ (i.e. ボトム型) です。
すべての型のスーパータイプである Any
のちょうど反対にある型です。
「すべての型のサブタイプ」という訳の分からないものなので、インスタンスが存在しません。 (コード上で Nothing
のインスタンスを作ることはできないです)
言語仕様だけだとよく分からない型ですが、使い道はちゃんと存在します。
Nothing 型の使い道
- 関数/ラムダ式の戻り値の型に指定
- 型引数に指定
- nullable Nothing
自身の経験上、使い道としてはこれらがあると認識してます。
それぞれの詳細について記述していきます。
戻り値が Nothing の関数は必ず例外を投げる
戻り値の型を Nothing
にすると、必ず例外を投げる関数になります。
(Nothing
型のインスタンスは存在しない → 戻り値を return できない → 例外を投げるしかない、という理屈らしい・・・)
別の言い方をすると throw 例外()
を関数化できるということです。
例えば「TODO として実装を後回しにしたい」場合に、標準ライブラリに TODO 関数が用意されており、戻り値の型が Nothing
です。
fun doSomething(): SomethingResult { TODO("後回し") }
見た目が分かり易い + 戻り値が無くてもコンパイルを通すことができるので、便利な関数だと思います。
戻り値が Nothing のラムダ式は例外を投げる or 呼び出し元から return
戻り値の型が Nothing
のラムダ式が例外を投げるのは関数と同じですが、ラムダ式を引数とする関数が inline fun
だった場合、 return
による関数の呼び出し元からの脱出も可能です。
inline fun
について- https://kotlinlang.org/docs/inline-functions.html
- 関数の呼び出し元にインライン展開される
- ラムダ式もインライン展開される
→ ラムダ式内のreturn
で関数の呼び出し元から脱出可能
inline fun
+ Nothing
ラムダ式のサンプルコードです。
// レシーバから null を取り除く関数 // null の場合は handleNull が実行される inline fun <T> T?.rejectNull(handleNull: () -> Nothing): T = this ?: handleNull() // 使用例 fun doSomething() { // null かもしれない結果を返す関数 val result = getNullableResult().rejectNull { println("結果が null なので doSomething から脱出") return // return の代わりに throw でも可 } // non-null な result に対して処理 }
型引数を省略できない場合に Nothing を指定できる
emptyList を変数に置く場合、型引数は何を使うのがベストでしょうか?
どんな型を型引数にしても問題は無いのですが、空の読み取り専用リスト (= 値が追加されることはない) であるため、 Nothing
がふさわしいと思います。
あまり無いコードだとは思いますが、 List<Nothing>
変数を空でないリストとあわせて使う場合も、型推論がうまく動作してくれます。
val empty = emptyList<Nothing>() listOf( listOf(1), empty ).forEach { println(it) // it は List<Int> に型推論される }
Nothing
がすべての型のサブタイプであるため、型推論候補に他のタイプがあればそちらが優先される、のような感じだと推測してます。
また、emptyList()
を変数に置かない場合でも、書き方によっては型推論してくれない場合があります。
// Kotlin 1.4.31 で確認 listOf( listOf("a") to true, // emptyList に型引数を指定しないとコンパイルエラー emptyList() to false ).forEach { (list, flag) -> println("list: $list | flag: $flag") }
型引数に String
を指定するのもアリですが、代わりに Nothing
でもコンパイル可能です。
特に、型引数が入れ子になって長くなる場合 (例: Map<String, ByteArray>
) に Nothing
と短く書けるのは便利です。
Nothing? 型は null しか入らない入れ物
nullable な Nothing
型は null しか入れることができません。
null しか入らないものの使い道はあまり無いのですが、 1 つだけ紹介できます。
ライブラリ・フレームワーク都合で、意味の無い引数を用意しなければならない場合があります。
その場合、 meaningless: Nothing? = null
のような引数がちょうどいい感じだと思います。
- デフォルト値があるため、呼び出し側で引数を書かなくていい
- 間違って引数を書いても null 以外入れることができない
→ 指定する必要の無いものだと気付くことができる
実例として mybatis があります。
interface SomethingMapper { // mybatis mapper 関数の特性として引数が「参照型 1 つ」の場合、 // SQL 内で使えるバインド変数 #{xxx} はそのプロパティになる // ↓ // この関数に meaningless 引数が無いと #{ids[0]} のようなバインド変数が使えない // // ※ 引数を増やす以外に、 @Param("ids") を引数に付ける方法もある @Select("SELECT * FROM something WHERE id IN (#{ids[0]}, #{ids[1]})") fun findSomethingByIds( ids: List<String>, meaningless: Nothing? = null ): List<Something> }
終わりに
Kotlin の Nothing
型の使い道について、自身の経験上から紹介してみました。
私自身、ボトム型の概念を初めて知ったのが Kotlin だったため、最初のうちは何のためにあるのかさっぱりでした。
ただ、その言語に触り続けているといつの間にか有用性に気付けたりします。
書いた人: プロダクトデベロップメント部 藤本泰輔