Slide 1

Slide 1 text

Kotlin Contracts 2018-11-07 長澤 太郎

Slide 2

Slide 2 text

長澤太郎 ● @ngsw_taro ● Ubie株式会社 ソフトウェアエンジニア ● エムスリー エンジニアフェロー ● 日本Kotlinユーザグループ代表

Slide 3

Slide 3 text

課題: 便利な関数があるのにスマートキャス   トできない(Kotlin 1.3より前) val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") }

Slide 4

Slide 4 text

課題: 便利な関数があるのにスマートキャス   トできない(Kotlin 1.3より前) val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") } stdlibの関数

Slide 5

Slide 5 text

課題: 便利な関数があるのにスマートキャス   トできない(Kotlin 1.3より前) val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") } Nullableのため 禁止されている呼び出し

Slide 6

Slide 6 text

課題: 便利な関数があるのにスマートキャス   トできない(Kotlin 1.3より前) val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") } val name: String? = user.name if (name != null && name.isNotBlank()) { println("Hello, ${name.capitalize()}") }

Slide 7

Slide 7 text

Kotlin 1.3 ではスマートキャストが効く! val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") } NotNullが保証されてるので 呼び出し可能

Slide 8

Slide 8 text

Contracts(契約) ● 契約による設計/契約プログラミング ● 事前条件 ○ 関数を呼び出すための条件を利用者が満たす ○ 例)型で不正な値を引数に取らせない ○ 例)require関数などで不正な値が渡ってきたら例外スロー ● 事後条件 ○ 関数を呼び出したあとの状況を関数が保証する ○ 例)list#addしたらサイズは1増えることを保証する ● 不変条件 ○ オブジェクトが満たすべき状態を維持する ○ 例)Nameオブジェクトは1字以上20字以下の英字を必ず持つ

Slide 9

Slide 9 text

Contracts(契約) ● 契約による設計/契約プログラミング ● 事前条件 ○ 関数を呼び出すための条件を利用者が満たす ○ 例)型で不正な値を引数に取らせない ○ 例)require関数などで不正な値が渡ってきたら例外スロー ● 事後条件 ○ 関数を呼び出したあとの状況を関数が保証する ○ 例)list#addしたらサイズは1増えることを保証する ● 不変条件 ○ オブジェクトが満たすべき状態を維持する ○ 例)Nameオブジェクトは1字以上20字以下の英字を必ず持つ Kotlin Contractsはここ しかも静的

Slide 10

Slide 10 text

契約を表明するDSL val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") } inline fun CharSequence?.isNullOrBlank(): Boolean { contract { returns(false) implies(this@isNullOrBlank != null) } return this == null || this.isBlank() }

Slide 11

Slide 11 text

契約を表明するDSL val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") } inline fun CharSequence?.isNullOrBlank(): Boolean { contract { returns(false) implies(this@isNullOrBlank != null) } return this == null || this.isBlank() }

Slide 12

Slide 12 text

契約を表明するDSL val name: String? = user.name if (!name.isNullOrBlank()) { println("Hello, ${name.capitalize()}") } inline fun CharSequence?.isNullOrBlank(): Boolean { contract { returns(false) implies(this@isNullOrBlank != null) } return this == null || this.isBlank() } falseが返されるとき、この文字列はNotNullであることを意味する

Slide 13

Slide 13 text

自分の関数にも契約を @ExperimentalContracts fun Any?.isNotNull(): Boolean { contract { returns(true) implies (this@isNotNull != null) } return this != null }

Slide 14

Slide 14 text

自分の関数にも契約を @ExperimentalContracts fun Any?.isNotNull(): Boolean { contract { returns(true) implies (this@isNotNull != null) } return this != null } DSL自体は不安定 バイナリ互換は維持される

Slide 15

Slide 15 text

契約対応している標準関数 kotlin.testも含む ● assertTrue ● check ● require ● assertFalse ● assertNotNull ● checkNotNull ● requireNotNull 引数がtrueを保証 引数がfalseを保証 引数がNotNullを保証

Slide 16

Slide 16 text

課題: val変数の初期化が絶対に成功するはず    なのにできない(Kotlin 1.3より前) val x: Int run { x = 12345 } println(x) ラムダの中での初期化を コンパイラはわかってくれ ない

Slide 17

Slide 17 text

Kotlin 1.3では「1度だけ呼び出される」契約によ り初期化OK val x: Int run { x = 12345 } println(x)

Slide 18

Slide 18 text

契約を表明するDSL val x: Int run { x = 12345 } inline fun run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }

Slide 19

Slide 19 text

契約を表明するDSL val x: Int run { x = 12345 } inline fun run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }

Slide 20

Slide 20 text

契約を表明するDSL val x: Int run { x = 12345 } inline fun run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } blockが「ちょうど1度だけ」呼び出される

Slide 21

Slide 21 text

InvocationKind ● AT_MOST_ONCE ● EXACTLY_ONCE ● AT_LEAST_ONCE ● UNKNOWN

Slide 22

Slide 22 text

契約対応している標準関数 kotlin.testも含む ● run ● with ● apply ● also ● let ● takeIf ● takeUnless ● repeat EXACTLY_ONCE UNKNOWN

Slide 23

Slide 23 text

契約DSLまとめ ● contract: 関数の先頭に置く必要がある ● returns(): 関数の実行が成功したら... ● returns(Any?): 引数の値 true|false|null を返したら... ● returnsNotNull(): NotNullを返したら... ● callsInPlace: 引数の関数が呼ばれる回数を保証 contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } contract { returns(true) implies (arg != null) }

Slide 24

Slide 24 text

総まとめ ● スマートキャストや変数初期化において、面倒なことがあった けどKotlin 1.3で解消された ● 契約DSLで契約を表明することでコンパイラに意味が伝わる ● 自分の関数に契約DSLを使うことができるが不安定 ● 標準ライブラリ関数で契約DSLを使っているものがあるが安 定 ● バイナリ互換あり