Kotlin Contractsはいつ使えるのか

nomoa,Kotlin

目的: compilerの制約は時にはプログラマーの意図に反して厳格すぎる時があるので明示的に緩める方法作りましょう。参照 (opens in a new tab) いつ使えるのか: 実際のアプリ実装で使える機会は少なそう。

contractsを実装するモチベーションとユースケース

呼び出し回数の宣言

scope functionであるrunを使用する際にimmutable variableに対してscope functionを利用して値を割り当てるとcompilerが正確に判断できない。 以下はrun functionからcontract宣言をコメントアウトしてあえて問題を起こしている例

fun test() {
  val x: Int
  myrun {
    x = 42 // compile error
  }
  println(x)
}

public inline fun <R> myrun(block: () -> R): R {
//  contract {
//    callsInPlace(block, InvocationKind.EXACTLY_ONCE)
//  }
  return block()
}

lambdaは通常何回呼ばれるか分からないのでimmutable変数の割り当てを禁止している。だがrun関数でimmutable変数を割り当てても一回だけ呼ばれることは自明。これを解決するためにコメントアウトしている部分のcontractを使用してcompilerに問題ないことを宣言し、上のようなコードを書けるようにする。

nullableかどうかの宣言

extension functionを作成する際に戻り値によって元のクラスの状態を宣言できる。 これも既にわかりやすい実装例があったので引用

@ExperimentalContracts
fun test() {
  val x: String? = null
  if (!x.myisNullOrEmpty()) {
    println(x.length) // compile error
  }
  println(x)
}

@ExperimentalContracts
public inline fun CharSequence?.myisNullOrEmpty(): Boolean {
//  contract {
//    returns(false) implies (this@isNullOrEmpty != null)
//  }

  return this == null || this.length == 0
}

contractが無いとif文の後でxはnullでは無いことは明らかだがcompilerは判断できないのでcontractによって制約を緩める。

コード解析の効率化

現状で一部のコード解析はプロジェクト全体で行うか全く行わないかの二択しかない。contract宣言によって厳格にチェックしたい箇所をユーザーが宣言する。これによって効率よくcompileを行う。意訳間違ってるかもしれないので元を呼んでください (opens in a new tab)

fun test() {
  var x: Int = 42
  thread {
    println("result is $x")
  }
  x = 43
}
// result is 43

上のケースではthreadの実行タイミングによって42と43のどちらが表示されるか分からない。これはユーザーの意図した動作では無いので警告を出せるようにする。

annotationを付与する必要あり

contractsが使用されている場所全てに

@ExperimentalContracts

付ける。 将来的に関数を破壊的に変更する可能性があるため

Top-levelで宣言する

まだ継承が実装できていないため

parameterへの参照はできない

// 生徒から授業を取り出す。この時授業の単位と教師わ割り当てられていない可能性がある。
data class DBReturnVal(
    val studentName: String,
    val lectureName: String,
    val lectureCredit: Int?,
    val teacherName: String?
)
@ExperimentalContracts
fun DBReturnVal.validLecture(): Boolean {
    contract {
        returns(true) implies (
                this@validLecture.lectureCredit != null 
// only references to parameters are allowed in description
                        && this@validLecture.teacherName != null)
    }
    return this.lectureCredit != null && this.teacherName != null
}

O/Rマッパーのvalidationとして使用できるかと考えたができない。contractsの実装方針として変数へ影響は及ぼさず、関数の役割を定義できるようにしていくと述べられているためこの機能が将来的に実装されるかは怪しい。

callsInPlace関数など確かにrunメソッドで使用されているが普通のアプリを作成する上では使用することはなかなか無さそう。String?.isNullOrEmptyの内部理解が進んだ点は良かった。しかしstableが出るまで動向を追うだけで実際の使用は待った方が賢明。

© nomoa.devRSS