LoginSignup
14
9

More than 3 years have passed since last update.

長澤さんの「Kotlin Contracts」を読んだまとめ

Last updated at Posted at 2018-11-22

この記事を書くきっかけは、長澤さんがKotlin1.3の新機能「Contracts」について発表していた資料が素晴らしかったので、復習も兼ねてアウトプットしようと思ったためです。

長澤さんの資料

Kotlin Contracts #m3kt - Speaker Deck
https://speakerdeck.com/ntaro/kotlin-contracts-number-m3kt

Contractsについて

1.3前はnullチェックしてもスマートキャストできない

  val name: String? = ""
  if (!name.isNullOrBlank()) { //stdlibの関数
      println("Hello, ${name.capitalize()}") // <= エラー
  }

エラーメッセージ

Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

「nullableなので ?. または !!. じゃないとダメだぞ」

これは自分のプロジェクトでも書いてて遭遇した。
null&空白チェックしてるのになぁ

1.3より前の場合、こうしなきゃいけなかった

  val name: String? = ""
  if (name != null && name.isNotBlank()) { // <= 1.3前
      println("Hello, ${name.capitalize()}")
  }

1.3からはスマートキャストが効く

  val name: String? = ""
  if (!name.isNullOrBlank()) {
      println("Hello, ${name.capitalize()}") // <= エラーにならない!
  }

Contracts(契約)とは

事後条件

  • 関数を呼び出したあとの状況を関数が保証する

ソースで見てみる

isNullOrBrank() の中を覗いてみる

public inline fun CharSequence?.isNullOrBlank(): Boolean {
    contract { // <= 注目ここから
        returns(false) implies (this@isNullOrBlank != null)
    } // <= ここまで
//falseが返されるとき、この文字列はNotNullであることを意味する、という契約
    return this == null || this.isBlank()
}

自作関数に契約

自作してみる

@ExperimentalContracts
fun Any?.isNotNull(): Boolean {
    contract { // <= 契約
        returns(true) implies (this@isNotNull != null)
    }
    // trueが返されるとき、thisはNotNullである契約
    return this != null
}

作ると分かるが、 @ExperimentalContracts が必要とIDEに怒られる

契約対応している標準関数

kotlin.testも含む

  • assertTrue
  • check
  • require
  • assetFalse
  • assertNOtNull
  • checkNotNull
  • requireNotNull

val変数が絶対初期化されるのにコンパイルエラーになる(Kotlin 1.3前)

val x: Int
run {
    x = 12345 // <= ラムダのなかで初期化してるのにコンパイルエラー
}
println(x)

Kotlin 1.3ではContractsによりこれが解決された

runの中を覗いてみる

public inline fun <T, R> T.run(block: T.() -> R): R {
    contract { // <= 契約を発見
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
// blockが「ちょうど一度だけ」呼び出される
    }
    return block()
}

InvocationKindの種類

  • AT_MOST_ONCE //関数のパラメータは一度呼び出されるか、まったく呼び出されません。
  • AT_LEAST_ONCE //関数のパラメータが1回以上呼び出されます。
  • EXACTLY_ONCE //関数パラメータは、1回だけ呼び出されます。
  • UNKNOWN //関数のパラメータが呼び出されますが、呼び出すことができる回数は不明です。

契約対応している標準関数

  • EXACTLY_ONCE
    • run
    • with
    • apply
    • also
    • let
    • takeIf
    • takeUnless
  • UNKNOWN

契約DSLまとめ

  • contract: 関数の先頭におく必要がある
  • returns(): 関数の実行が成功したら
  • returns(Any?): 引数の値 true|false|null を返したら
  • returnNotNull(): NotNullを返したら
  • callsInPlace: 引数の関数が呼ばれる回数を保証

まとめ

  • スマートキャストや変数初期化において、面倒なことがKotlin 1.3で解消された
  • 契約DSLで契約を表明することでコンパイラに意味が伝わる
14
9
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
14
9