New Type Inference & Related Language Features Svetlana Isakova @sveta_isakova
Agenda Experimental features Contracts New type inference
Experimental features
Experimental features Our goal: to let new features be tried by early adopters as soon as possible
Example: Coroutines Kotlin 1.2 import kotlinx.coroutines.experimental.* might be stable enough, but no backward compatibility guarantees Kotlin 1.3 import kotlinx.coroutines.* backward compatibility guarantees
Automatic migration
Experimental Language Features • you need to explicitly opt in at the call site to use experimental features kotlin { experimental { coroutines 'enable' } }
Experimental API for Libraries • can be publicly released as a part of the library • may break at any moment
Experimental API You can mark your shiny new class or function as experimental @Experimental annotation class ShinyNewAPI @ShinyNewAPI class Foo { ... }
Using experimental API @UseExperimental(ShinyNewAPI:: class ) fun doSomethingImportant() { val foo = Foo() ... }
Experimental: Summary • feedback loop for new features and API
Contracts
Changes in standard library
Changes in standard library inline fun <R> run(block: () -> R): R = block()
Changes in standard library 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 inline fun <R> run(block: () -> R): R = block() inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind. EXACTLY_ONCE ) } return block() }
We know something about run , which the compiler doesn’t val answer: Int run { answer = 42 } println (answer)
We know something about run , which the compiler doesn’t val answer: Int run { answer = 42 } Compiler error: Captured values initialization is forbidden due to possible reassignment println (answer)
We know something about isNullOrEmpty , which the compiler doesn’t val s: String? = "" if (!s. isNullOrEmpty ()) { s. first () }
We know something about isNullOrEmpty , which the compiler doesn’t 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?
Kotlin Contracts …allow to share extra information about code semantics with the compiler
Variable initialization inside run val answer: Int run { answer = 42 } println (answer)
Variable initialization inside run val answer: Int run { ✓ answer = 42 } println (answer)
Making smart casts even smarter val s: String? = "" if (!s. isNullOrEmpty ()) { s. first () }
Making smart casts even smarter val s: String? = "" if (!s. isNullOrEmpty ()) { ✓ s. first () }
Contract: block lambda will be always called once inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind. EXACTLY_ONCE ) } return block() }
Contract for calling inlined lambda in-place run, let, with, apply, also takeIf, takeUnless, synchronized
Contract: if the function returns false , the receiver is not-null 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 fun String?.isNullOrEmpty(): Boolean { contract { returns( false ) implies ( this @isNullOrEmpty != null ) } return this == null || this . length == 0 } val s: String? = "" if (!s. isNullOrEmpty ()) { ✓ s. first () }
Contract: if the function returns false , the receiver is not-null 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 fun String?.isNullOrEmpty(): Boolean { contract { this: ContractBuilder returns( false ) implies ( this @isNullOrEmpty != null ) } return this == null || this . length == 0 }
Contract: if the function returns a given value, a condition is satisfied isNullOrEmpty, isNullOrBlank kotlin.test: assertTrue, assertFalse, assertNotNull check, checkNotNull, require, requireNotNull
Kotlin Contract experimental extra information by developer & compiler uses this information for code analysis
Kotlin Contract extra information by developer & compiler uses this information for code analysis & to be supported checking that the information is correct at compile time or runtime
Why can’t compiler just implicitly infer such information?
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
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
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 () } ) }
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 () } ) }
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 () } ) }
Using contracts for your own functions fun assertNotNull(actual: Any?, message: String? = null ) { contract { returns() implies (actual != null ) } Experimental if (actual == null ) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull (input) assertTrue (input. all { it . isDigit () } ) }
Using contracts for your own functions fun assertNotNull(actual: Any?, message: String? = null ) { contract { returns() implies (actual != null ) } Experimental if (actual == null ) { throw AssertionError( message ?: "Value must not be null" ) } } fun testInput(input: String?) { assertNotNull (input) assertTrue (input. all { it . isDigit () } ) }
Contracts: Summary • handy functions ( run , isEmptyOrNull ) are even more useful • contract DSL will change • you can go and try it out
New type inference
New type inference better and more powerful type inference new features are supported
Might be turned on in Kotlin 1.3 kotlin { experimental { newInference 'enable' } }
Kotlin libraries • Libraries should specify return types for public API • Overloaded functions must do the same thing
Kotlin libraries • Libraries should specify return types for public API turn on an IDE inspection “Public API declaration has implicit return type” • Overloaded functions must do the same thing
New type inference • SAM conversions for Kotlin functions • better inference for builders • better inference for call chains • better inference for intersection types
SAM conversions for Kotlin functions
SAM conversions for Kotlin functions public interface Action<T> { void execute(T target); } fun handleInput(handler: Action<String>) { ... }
SAM conversions for Kotlin functions public interface Action<T> { void execute(T target); } fun handleInput(handler: Action<String>) { ... } You can pass a lambda as an argument when a Java SAM-interface is expected: handleInput { println (it) }
Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction { x, y -> x + y } )
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
Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction { x, y -> x + y } ) New inference: observable.zipWith(anotherObservable) { x, y -> x + y }
Support for several SAM arguments Old inference: observable.zipWith(anotherObservable, BiFunction { x, y -> x + y } ) New inference: observable.zipWith(anotherObservable) { x, y -> x + y } ✓
Builder inference
Inference for sequence val seq = sequence { yield(42) }
Inference for sequence : Sequence<Int> val seq = sequence { yield(42) }
Recommend
More recommend