車の暖房が壊れて、数日間凍えながら出社していました。kiyokawaです。
Kotlin1.3から追加された「contract」を試してみました。
KotlinのStringにはisNullOrEmpty()という値がnullでも空文字でもないということを確認できる便利な関数があります。(正確には、CharSequenceの拡張関数)
しかし、Kotlin1.3より前はisNullOrEmpty()ではスマートキャストが効かず、ifの中でもNullableなStringとして扱わなければなりませんでした。
1 2 3 4 5 |
val s: String? = "hello" if (!s.isNullOrEmpty()) { // スマートキャストが効かずコンパイルエラー println(s.toUpperCase()) } |
ですが、Kotlin1.3で導入されたcontractという機能のおかげでisNullOrEmpty()でもスマートキャストが効くようになりました。
関数の定義を見てみると以下のようになっています。
1 2 3 4 5 6 7 |
public inline fun CharSequence?.isNullOrEmpty(): Boolean { contract { returns(false) implies (this@isNullOrEmpty != null) } return this == null || this.length == 0 } |
関数の先頭に出てきましたね、contract。
contractは関数を呼び出したあとの状況を関数が保証するという機能です。
今回の関数のcontractを説明すると
1 2 3 4 5 |
contract { // returns(false) falseが返却されるとき // implies (this@isNullOrEmpty != null) 文字がnullではないことを意味する returns(false) implies (this@isNullOrEmpty != null) } |
となります。
以上を踏まえて、お試しにAnyがString型かどうかを確認するcontractを作ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@ExperimentalContracts fun Any?.isString(): Boolean { contract { // trueが返却されるとき、thisがString型であることを意味する returns(true) implies (this@isString is String) } return this is String } val any: Any? = "test" if (any.isString()) { // スマートキャストが効いているのでNonNullのStringとして扱える any.toUpperCase() } |
今回はお試しで作ってみたものなので、実際にAnyがStringかどうか確認するときは普通に
1 2 3 4 5 6 7 8 |
if (any is String) { any.toUpperCase() } when (any) { is String -> any.toUpperCase() else -> doSomething() } |
みたいな感じでやってしまうとは思います。
実はこのお試しの例を作る前にEitherという成功(Right)、失敗(Left)を表すクラスにisRight()を作ってifの中では成功時の結果を取得できるように作ってみたんですが、contractではジェネリクスを使うことは出来ないようだったので断念しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
sealed class Either data class Left(val value: T) : Either<T, Nothing>() data class Right(val value: T) : Either<Nothing, T>() @ExperimentalContracts fun <L, R> Either<L, R>.isRight(): Boolean { contract { returns(true) implies (this@isRight is Right) } return this is Right } val result = getUser() if (result.isRight()) { // resultがRight型にスマートキャストされているが、ジェネリクスまで保証されていないので // result.valueの型がR型になっている val user = result.value } |
まとめ
・contractを使いコンパイラに状況を保証することでスマートキャストが効くようになる
・contractはExperimentalなので、実際に使うときは注意してください
・Experimentalが外れるまではcontractを使った関数と、利用側には必ず@ExperimentalContractsを付ける必要があります
・Kotlin1.3からisNullOrEmpty()でスマートキャストが効くようになった理由が分かって良かったです