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

New Type Inference and Related Language Features

New Type Inference and Related Language Features

The talk @KotlinConf 2018.

Svetlana Isakova

October 04, 2018
Tweet

More Decks by Svetlana Isakova

Other Decks in Programming

Transcript

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

    by early adopters as soon as possible
  2. 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
  3. Experimental Language Features • you need to explicitly opt in

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

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

    class ShinyNewAPI You can mark your shiny new class or function as experimental
  6. 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
  7. 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()
  8. We know something about run, which the compiler doesn’t val

    answer: Int run { answer = 42 } println(answer)
  9. 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
  10. val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }

    We know something about isNullOrEmpty, which the compiler doesn’t
  11. 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
  12. val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }

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

    { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } Contract: block lambda will be always called once
  14. 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
  15. 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() } ✓
  16. 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
  17. 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
  18. Contract: if the function returns a given value, a condition

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

    information for code analysis experimental
  20. 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
  21. 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
  22. 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
  23. 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() }) }
  24. 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() }) }
  25. 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() }) }
  26. 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() }) }
  27. 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() }) }
  28. • handy functions (run, isEmptyOrNull) are even more useful •

    contract DSL will change • you can go and try it out Contracts: Summary
  29. Kotlin libraries • Libraries should specify return types for public

    API • Overloaded functions must do the same thing
  30. 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”
  31. New type inference • SAM conversions for Kotlin functions •

    better inference for builders • better inference for call chains • better inference for intersection types
  32. fun handleInput(handler: Action<String>) { ... } SAM conversions for Kotlin

    functions public interface Action<T> { void execute(T target); }
  33. 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) }
  34. 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
  35. Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction {

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

    x, y -> x + y }) observable.zipWith(anotherObservable) { x, y -> x + y } New inference: ✓
  37. Inference for regular lambdas people.map { person -> println("Processed: ${person.name}")

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

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

    person.age } List<Int> Int Result type may depend on lambda return type
  40. 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
  41. 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>
  42. 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
  43. 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
  44. 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
  45. Better inference for intersection types interface Drownable interface Throwable fun

    <T> throwIntoRiver(thing: T) where T : Drownable, T : Throwable { println("Bye, $thing") }
  46. 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) }
  47. 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) }
  48. 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) } }
  49. 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() }) }
  50. 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
  51. Improving assertNotNull: return asserted value fun <S: CharSequence> testInput(input: S?)

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

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

    will be used by default in the future • you can go and try it out