New Type Inference and Related Language Features

New Type Inference and Related Language Features

The talk @KotlinConf 2018.

C6997ce411091d4a51ea3caa2109c0b0?s=128

Svetlana Isakova

October 04, 2018
Tweet

Transcript

  1. Svetlana Isakova @sveta_isakova New Type Inference & Related Language Features

  2. Agenda Experimental features Contracts New type inference

  3. Experimental features

  4. Experimental features Our goal: to let new features be tried

    by early adopters as soon as possible
  5. import kotlinx.coroutines.experimental.* import kotlinx.coroutines.* Example: Coroutines Kotlin 1.3 Kotlin 1.2

    might be stable enough, but no backward compatibility guarantees backward compatibility guarantees
  6. Automatic migration

  7. Experimental Language Features • you need to explicitly opt in

    at the call site to use experimental features kotlin { experimental { coroutines 'enable' } }
  8. Experimental API for Libraries • can be publicly released as

    a part of the library • may break at any moment
  9. @ShinyNewAPI class Foo { ... } Experimental API @Experimental annotation

    class ShinyNewAPI You can mark your shiny new class or function as experimental
  10. Using experimental API @UseExperimental(ShinyNewAPI::class) fun doSomethingImportant() { val foo =

    Foo() ... }
  11. • feedback loop for new features and API Experimental: Summary

  12. Contracts

  13. Changes in standard library

  14. inline fun <R> run(block: () -> R): R = block()

    Changes in standard library
  15. inline fun <R> run(block: () -> R): R = block()

    inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } Changes in standard library
  16. Changes in standard library inline fun <R> run(block: () ->

    R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } inline fun <R> run(block: () -> R): R = block()
  17. We know something about run, which the compiler doesn’t val

    answer: Int run { answer = 42 } println(answer)
  18. We know something about run, which the compiler doesn’t val

    answer: Int run { answer = 42 } println(answer) Compiler error: Captured values initialization is forbidden due to possible reassignment
  19. val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }

    We know something about isNullOrEmpty, which the compiler doesn’t
  20. val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }

    Compiler error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? We know something about isNullOrEmpty, which the compiler doesn’t
  21. Kotlin Contracts …allow to share extra information about code semantics

    with the compiler
  22. Variable initialization inside run val answer: Int run { answer

    = 42 } println(answer)
  23. Variable initialization inside run val answer: Int run { answer

    = 42 } println(answer) ✓
  24. val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }

    Making smart casts even smarter
  25. val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }

    Making smart casts even smarter ✓
  26. inline fun <R> run(block: () -> R): R { contract

    { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } Contract: block lambda will be always called once
  27. Contract for calling inlined lambda in-place run, let, with, apply,

    also takeIf, takeUnless, synchronized
  28. fun String?.isNullOrEmpty(): Boolean { contract { returns(false) implies (this@isNullOrEmpty !=

    null) } return this == null || this.length == 0 } Contract: if the function returns false, the receiver is not-null
  29. fun String?.isNullOrEmpty(): Boolean { contract { returns(false) implies (this@isNullOrEmpty !=

    null) } return this == null || this.length == 0 } Contract: if the function returns false, the receiver is not-null val s: String? = "" if (!s.isNullOrEmpty()) { s.first() } ✓
  30. fun String?.isNullOrEmpty(): Boolean { contract { returns(false) implies (this@isNullOrEmpty !=

    null) } return this == null || this.length == 0 } Contract: if the function returns false, the receiver is not-null
  31. this: ContractBuilder fun String?.isNullOrEmpty(): Boolean { contract { returns(false) implies

    (this@isNullOrEmpty != null) } return this == null || this.length == 0 } Contract: if the function returns false, the receiver is not-null
  32. Contract: if the function returns a given value, a condition

    is satisfied isNullOrEmpty, isNullOrBlank kotlin.test: assertTrue, assertFalse, assertNotNull check, checkNotNull, require, requireNotNull
  33. Kotlin Contract extra information by developer & compiler uses this

    information for code analysis experimental
  34. Kotlin Contract extra information by developer & compiler uses this

    information for code analysis & checking that the information is correct at compile time or runtime to be supported
  35. Why can’t compiler just implicitly infer such information?

  36. Why can’t compiler just implicitly infer such information? Because then

    such implicitly inferred information: - can be implicitly changed - can implicitly break code depending on it
  37. Why can’t compiler just implicitly infer such information? Because then

    such implicitly inferred information: - can be implicitly changed - can implicitly break code depending on it Contract = explicit statement about function behaviour
  38. Using contracts for your own functions fun assertNotNull(actual: Any?, message:

    String? = null) { if (actual == null) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull(input) assertTrue(input!!.any { it.isDigit() }) }
  39. Using contracts for your own functions fun assertNotNull(actual: Any?, message:

    String? = null) { if (actual == null) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull(input) assertTrue(input!!.any { it.isDigit() }) }
  40. Using contracts for your own functions fun assertNotNull(actual: Any?, message:

    String? = null) { contract { returns() implies (actual != null) } if (actual == null) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull(input) assertTrue(input.all { it.isDigit() }) }
  41. Experimental Using contracts for your own functions fun assertNotNull(actual: Any?,

    message: String? = null) { contract { returns() implies (actual != null) } if (actual == null) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull(input) assertTrue(input.all { it.isDigit() }) }
  42. Experimental Using contracts for your own functions fun assertNotNull(actual: Any?,

    message: String? = null) { contract { returns() implies (actual != null) } if (actual == null) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull(input) assertTrue(input.all { it.isDigit() }) }
  43. • handy functions (run, isEmptyOrNull) are even more useful •

    contract DSL will change • you can go and try it out Contracts: Summary
  44. New type inference

  45. New type inference better and more powerful type inference new

    features are supported
  46. kotlin { experimental { newInference 'enable' } } Might be

    turned on in Kotlin 1.3
  47. Kotlin libraries • Libraries should specify return types for public

    API • Overloaded functions must do the same thing
  48. Kotlin libraries • Libraries should specify return types for public

    API • Overloaded functions must do the same thing turn on an IDE inspection “Public API declaration has implicit return type”
  49. New type inference • SAM conversions for Kotlin functions •

    better inference for builders • better inference for call chains • better inference for intersection types
  50. SAM conversions for Kotlin functions

  51. fun handleInput(handler: Action<String>) { ... } SAM conversions for Kotlin

    functions public interface Action<T> { void execute(T target); }
  52. fun handleInput(handler: Action<String>) { ... } SAM conversions for Kotlin

    functions public interface Action<T> { void execute(T target); } You can pass a lambda as an argument when a Java SAM-interface is expected: handleInput { println(it) }
  53. Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction {

    x, y -> x + y })
  54. Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction {

    x, y -> x + y }) class Observable { public final Observable zipWith( ObservableSource other, BiFunction zipper) {…} } SAM interfaces
  55. Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction {

    x, y -> x + y }) observable.zipWith(anotherObservable) { x, y -> x + y } New inference:
  56. Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction {

    x, y -> x + y }) observable.zipWith(anotherObservable) { x, y -> x + y } New inference: ✓
  57. Builder inference

  58. Inference for sequence val seq = sequence { yield(42) }

  59. Inference for sequence val seq = sequence { yield(42) }

    : Sequence<Int>
  60. Inference for regular lambdas people.map { person -> println("Processed: ${person.name}")

    person.age } Result type may depend on lambda return type
  61. Inference for regular lambdas people.map { person -> println("Processed: ${person.name}")

    person.age } Int Result type may depend on lambda return type
  62. Inference for regular lambdas people.map { person -> println("Processed: ${person.name}")

    person.age } List<Int> Int Result type may depend on lambda return type
  63. Inference for builder lambdas val seq = sequence { yield(cat)

    println("adding more elements") yield(dog) } Result type may depend only on lambda return type Result type may depend on calls inside lambda
  64. Inference for builder lambdas val seq = sequence { yield(cat)

    println("adding more elements") yield(dog) } Result type may depend only on lambda return type Result type may depend on calls inside lambda : Sequence<Animal>
  65. Builder inference automatically works for sequence in 1.3 opt in

    to use that for your functions
  66. Using @BuilderInference for your function fun <T> buildList( @BuilderInference init:

    MutableList<T>.() -> Unit ): List<T> { return mutableListOf<T>().apply(init) } val list = buildList { add(cat) add(dog) } : List<Animal> Experimental
  67. Inference for call chains

  68. Inference for call chains fun createMap() = MapBuilder() .put("answer", 42)

    .build() Old inference: One call is analyzed at a time New inference: A call chain is analyzed
  69. Inference for call chains fun createMap() = MapBuilder() .put("answer", 42)

    .build() : Map<String, Int> Old inference: One call is analyzed at a time New inference: A call chain is analyzed
  70. Intersection types

  71. Better inference for intersection types interface Drownable interface Throwable fun

    <T> throwIntoRiver(thing: T) where T : Drownable, T : Throwable { println("Bye, $thing") }
  72. Better inference for intersection types interface Drownable interface Throwable fun

    <T> throwIntoRiver(thing: T) where T : Drownable, T : Throwable { println("Bye, $thing") } if (something is Drownable && something is Throwable) { throwIntoRiver(something) }
  73. Drownable & Throwable Better inference for intersection types interface Drownable

    interface Throwable fun <T> throwIntoRiver(thing: T) where T : Drownable, T : Throwable { println("Bye, $thing") } if (something is Drownable && something is Throwable) { throwIntoRiver(something) }
  74. Intersection type to denote not-nullable generic type T!! = T

    & Any
  75. Proper type for T!! fun <T> describe(x: T) { println(x!!::class.simpleName)

    }
  76. Proper type for T!! fun <T> describe(x: T) { println(x!!::class.simpleName)

    } T!!
  77. Proper type for T!! fun <T> describe(x: T) { println(x!!::class.simpleName)

    } T!! T!! fun <T> describe(x: T) { if (x != null) { println(x::class.simpleName) } }
  78. Improving assertNotNull fun assertNotNull(actual: Any?, message: String? = null) {

    if (actual == null) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull(input) assertTrue(input!!.all { it.isDigit() }) }
  79. fun testInput(input: String?) { assertTrue(assertNotNull(input).all { it.isDigit() }) } Improving

    assertNotNull: return asserted value
  80. fun <T : Any> assertNotNull( actual: T?, message: String? =

    null ): T { if (actual == null) { throw AssertionError( message ?: "Value must not be null" ) } return actual } fun testInput(input: String?) { assertTrue(assertNotNull(input).all { it.isDigit() }) } Improving assertNotNull: return asserted value
  81. Improving assertNotNull: return asserted value fun <S: CharSequence> testInput(input: S?)

    { assertTrue(assertNotNull(input).all { it.isDigit() }) }
  82. Improving assertNotNull: return asserted value fun <S: CharSequence> testInput(input: S?)

    { assertTrue(assertNotNull(input).all { it.isDigit() }) } S!! S!! = S & Any
  83. New type inference: summary • new features are supported •

    will be used by default in the future • you can go and try it out
  84. Have a nice Kotlin! #kotlinconf18