Discovering Kotlin Contracts

Discovering Kotlin Contracts

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

3dc29e8cfc6ef333e2b41a1b0e826b57?s=128

Nicola Corti

October 02, 2018
Tweet

Transcript

  1. Discovering Kotlin Contracts Nicola Corti @cortinico

  2. 1.3

  3. The Problem

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

  5. The Problem @Test fun testMyTokenLength() { val token : String?

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

    = getMyToken(); assertNotNull(token) }
  7. 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?
  8. The Problem @Test fun testMyTokenLength() { val token : String?

    = getMyToken(); assertNotNull(token) assertEquals(42, token?.length) }
  9. 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
  10. Syntax

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

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

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

    effects here } return this != null && this != 0 }
  14. Effects fun isValid(number: Int?) : Boolean Int? Boolean Effect1, Effect2,

    Effect3
  15. Returns fun returns(): SimpleEffect fun returns(value: Any?): SimpleEffect fun returnsNotNull():

    SimpleEffect interface SimpleEffect : Effect { infix fun implies(boolExpr: Boolean): ConditionalEffect } ConditionalEffect
  16. Syntax fun Int?.isValid(): Boolean { contract { } return this

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

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

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

    != null) } return this != null && this != 0 }
  20. Syntax val aInt : Int? = getAnInt() if (aInt.isValid()){ }

    else { }
  21. 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 { }
  22. 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 }
  23. The Problem @Test fun testMyTokenLenght() { val token : String?

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

    = getMyToken(); assertNotNull(token) assertEquals(42, token.length) }
  25. The Problem fun assertNotNull(actual: Any?) { }

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

  27. The Problem fun assertNotNull(actual: Any?) { contract { returns() implies

    (actual != null) } org.junit.Assert.assertNotNull(actual) }
  28. 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 <R> callsInPlace( lambda: Function<R>, kind: InvocationKind = InvocationKind.UNKNOWN ): CallsInPlace
  29. CallsInPlace InvocationKind.UNKNOWN

  30. CallsInPlace InvocationKind.UNKNOWN InvocationKind.AT_MOST_ONCE

  31. CallsInPlace InvocationKind.UNKNOWN InvocationKind.AT_MOST_ONCE InvocationKind.AT_LEAST_ONCE

  32. CallsInPlace InvocationKind.UNKNOWN InvocationKind.AT_MOST_ONCE InvocationKind.AT_LEAST_ONCE InvocationKind.EXACTLY_ONCE

  33. CallsInPlace fun main(){ val x : Int? run { x

    = 10 } println(x) }
  34. 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
  35. CallsInPlace public inline fun <T, R> T.run(block: T.() -> R):

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

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

    R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
  38. CallsInPlace fun main(){ val x : Int? run { x

    = 10 } println(x) }
  39. 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.
  40. Thanks! Nicola Corti @cortinico bit.ly/ktcontracts bit.ly/ktcontracts-slides