Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Discovering Kotlin Contracts

Discovering Kotlin Contracts

Slides for me lightning talk on Kotlin Contracts @ GDG Hamburg Android

Nicola Corti

October 02, 2018
Tweet

More Decks by Nicola Corti

Other Decks in Technology

Transcript

  1. Discovering
    Kotlin Contracts
    Nicola Corti
    @cortinico

    View full-size slide

  2. The Problem
    @Test fun testMyTokenLength() {
    }

    View full-size slide

  3. The Problem
    @Test fun testMyTokenLength() {
    val token : String? = getMyToken();
    }

    View full-size slide

  4. The Problem
    @Test fun testMyTokenLength() {
    val token : String? = getMyToken();
    assertNotNull(token)
    }

    View full-size slide

  5. The Problem
    @Test fun testMyTokenLength() {
    val token : String? = getMyToken();
    assertNotNull(token)
    assertEquals(42, token.length)
    }
    Error(5, 22): Only safe (?.) or non-null
    asserted (!!.) calls are allowed on a
    nullable receiver of type String?

    View full-size slide

  6. The Problem
    @Test fun testMyTokenLength() {
    val token : String? = getMyToken();
    assertNotNull(token)
    assertEquals(42, token?.length)
    }

    View full-size slide

  7. Kotlin Contracts
    • The Compiler is not smart enough to

    understand the context
    • You know something that the compiler doesn’t know
    • We can provide this extra knowledge with a Kotlin Contract
    • The goal is to write better/cleaner code
    Experimental features

    View full-size slide

  8. Syntax
    fun Int?.isValid(): Boolean {
    return this != null && this != 0
    }

    View full-size slide

  9. Syntax
    fun Int?.isValid(): Boolean {
    contract { }
    return this != null && this != 0
    }

    View full-size slide

  10. Syntax
    fun Int?.isValid(): Boolean {
    contract {
    // Place your effects here
    }
    return this != null && this != 0
    }

    View full-size slide

  11. Effects
    fun isValid(number: Int?) : Boolean
    Int? Boolean
    Effect1, Effect2, Effect3

    View full-size slide

  12. Returns
    fun returns(): SimpleEffect
    fun returns(value: Any?): SimpleEffect
    fun returnsNotNull(): SimpleEffect
    interface SimpleEffect : Effect {
    infix fun implies(boolExpr: Boolean): ConditionalEffect
    }
    ConditionalEffect

    View full-size slide

  13. Syntax
    fun Int?.isValid(): Boolean {
    contract { }
    return this != null && this != 0
    }

    View full-size slide

  14. Syntax
    fun Int?.isValid(): Boolean {
    contract {
    returns(true)
    }
    return this != null && this != 0
    }

    View full-size slide

  15. Syntax
    fun Int?.isValid(): Boolean {
    contract {
    returns(true) implies
    }
    return this != null && this != 0
    }

    View full-size slide

  16. Syntax
    fun Int?.isValid(): Boolean {
    contract {
    returns(true) implies (this@isValid != null)
    }
    return this != null && this != 0
    }

    View full-size slide

  17. Syntax
    val aInt : Int? = getAnInt()
    if (aInt.isValid()){
    } else {
    }

    View full-size slide

  18. Syntax
    val aInt : Int? = getAnInt()
    if (aInt.isValid()){
    // Here the compiler knows that `aInt` is not nullable
    // thanks to the contract on `isValid`
    aInt.absoluteValue
    } else {
    }

    View full-size slide

  19. Syntax
    val aInt : Int? = getAnInt()
    if (aInt.isValid()){
    // Here the compiler knows that `aInt` is not nullable
    // thanks to the contract on `isValid`
    aInt.absoluteValue
    } else {
    // Here the compiler has no extra information since
    // the `isValid` has only a `returns(true)` effect
    aInt?.absoluteValue
    }

    View full-size slide

  20. The Problem
    @Test fun testMyTokenLenght() {
    val token : String? = getMyToken();
    assertNotNull(token)
    assertEquals(42, token?.length)
    }

    View full-size slide

  21. The Problem
    @Test fun testMyTokenLenght() {
    val token : String? = getMyToken();
    assertNotNull(token)
    assertEquals(42, token.length)
    }

    View full-size slide

  22. The Problem
    fun assertNotNull(actual: Any?) {
    }

    View full-size slide

  23. The Problem
    fun assertNotNull(actual: Any?) {
    org.junit.Assert.assertNotNull(actual)
    }

    View full-size slide

  24. The Problem
    fun assertNotNull(actual: Any?) {
    contract {
    returns() implies (actual != null)
    }
    org.junit.Assert.assertNotNull(actual)
    }

    View full-size slide

  25. CallsInPlace
    • lambda will not be called after the call to owner-function is
    finished.
    • lambda will not be passed to another function that 

    doesn’t have a similar contract.
    • lambda will be called the specified amount of times (kind)
    fun callsInPlace(
    lambda: Function,
    kind: InvocationKind = InvocationKind.UNKNOWN
    ): CallsInPlace

    View full-size slide

  26. CallsInPlace
    InvocationKind.UNKNOWN

    View full-size slide

  27. CallsInPlace
    InvocationKind.UNKNOWN
    InvocationKind.AT_MOST_ONCE

    View full-size slide

  28. CallsInPlace
    InvocationKind.UNKNOWN
    InvocationKind.AT_MOST_ONCE
    InvocationKind.AT_LEAST_ONCE

    View full-size slide

  29. CallsInPlace
    InvocationKind.UNKNOWN
    InvocationKind.AT_MOST_ONCE
    InvocationKind.AT_LEAST_ONCE
    InvocationKind.EXACTLY_ONCE

    View full-size slide

  30. CallsInPlace
    fun main(){
    val x : Int?
    run {
    x = 10
    }
    println(x)
    }

    View full-size slide

  31. CallsInPlace
    fun main(){
    val x : Int?
    run {
    x = 10
    }
    println(x)
    }
    Error:(6, 18) Variable ‘x’ must be
    initialized
    Error:(4, 8) Captured values
    initialization is forbidden due to
    possible reassignment

    View full-size slide

  32. CallsInPlace
    public inline fun T.run(block: T.() -> R): R {
    return block()
    }

    View full-size slide

  33. CallsInPlace
    public inline fun T.run(block: T.() -> R): R {
    contract {
    }
    return block()
    }

    View full-size slide

  34. CallsInPlace
    public inline fun T.run(block: T.() -> R): R {
    contract {
    callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
    }

    View full-size slide

  35. CallsInPlace
    fun main(){
    val x : Int?
    run {
    x = 10
    }
    println(x)
    }

    View full-size slide

  36. Limitations
    • They are not verified. 

    You’re responsible for writing correct contracts.
    • returns(value) and implies allowed values are limited
    • Only to top-level and block functions.

    View full-size slide

  37. Thanks!
    Nicola Corti
    @cortinico
    bit.ly/ktcontracts
    bit.ly/ktcontracts-slides

    View full-size slide