Svetlana Isakova @sveta_isakova
New Type Inference & Related Language Features Svetlana Isakova - - PowerPoint PPT Presentation
New Type Inference & Related Language Features Svetlana Isakova - - PowerPoint PPT Presentation
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
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
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
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
@ShinyNewAPI class Foo { ... }
Experimental API
@Experimental annotation class ShinyNewAPI
You can mark your shiny new class or function as experimental
Using experimental API
@UseExperimental(ShinyNewAPI::class) fun doSomethingImportant() { val foo = Foo() ... }
- feedback loop for new features and API
Experimental: Summary
Contracts
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
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()
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 } println(answer)
Compiler error: Captured values initialization is forbidden due to possible reassignment
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?
We know something about isNullOrEmpty, which the compiler doesn’t
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)
✓
val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }
Making smart casts even smarter
val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }
Making smart casts even smarter
✓
inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
Contract: block lambda will be always called once
Contract for calling inlined lambda in-place
run, let, with, apply, also takeIf, takeUnless, synchronized
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 }
Contract: if the function returns false, the receiver is not-null
val s: String? = "" if (!s.isNullOrEmpty()) { s.first() }
✓
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
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
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
extra information by developer & compiler uses this information for code analysis
experimental
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
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() }) }
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() }) }
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() }) }
- handy functions (run, isEmptyOrNull)
are even more useful
- contract DSL will change
- you can go and try it out
Contracts: Summary
New type inference
New type inference
better and more powerful type inference new features are supported
kotlin { experimental { newInference 'enable' } }
Might be turned on in Kotlin 1.3
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
- Overloaded functions must do the same thing
turn on an IDE inspection “Public API declaration has implicit return type”
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
fun handleInput(handler: Action<String>) { ... }
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); }
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:
- bservable.zipWith(anotherObservable,
BiFunction { x, y -> x + y })
Support for several SAM arguments
Old inference:
- bservable.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:
- bservable.zipWith(anotherObservable,
BiFunction { x, y -> x + y })
- bservable.zipWith(anotherObservable) { x, y -> x + y }
New inference:
Support for several SAM arguments
Old inference:
- bservable.zipWith(anotherObservable,
BiFunction { x, y -> x + y })
- bservable.zipWith(anotherObservable) { x, y -> x + y }
New inference:
✓
Builder inference
Inference for sequence
val seq = sequence { yield(42) }
Inference for sequence
val seq = sequence { yield(42) } : Sequence<Int>
Inference for regular lambdas
people.map { person -> println("Processed: ${person.name}") person.age }
Result type may depend on lambda return type
Inference for regular lambdas
people.map { person -> println("Processed: ${person.name}") person.age } Int
Result type may depend on lambda return type
Inference for regular lambdas
people.map { person -> println("Processed: ${person.name}") person.age } List<Int> Int
Result type may depend on lambda return type
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
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>
Builder inference
automatically works for sequence in 1.3
- pt in to use that for your functions
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
Inference for call chains
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
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
Intersection types
Better inference for intersection types
interface Drownable interface Throwable fun <T> throwIntoRiver(thing: T) where T : Drownable, T : Throwable { println("Bye, $thing") }
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) }
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) }
Intersection type to denote not-nullable generic type
T!! = T & Any
Proper type for T!!
fun <T> describe(x: T) { println(x!!::class.simpleName) }
Proper type for T!!
fun <T> describe(x: T) { println(x!!::class.simpleName) } T!!
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) } }
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() }) }
fun testInput(input: String?) { assertTrue(assertNotNull(input).all { it.isDigit() }) }
Improving assertNotNull: return asserted value
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
Improving assertNotNull: return asserted value
fun <S: CharSequence> testInput(input: S?) { assertTrue(assertNotNull(input).all { it.isDigit() }) }
Improving assertNotNull: return asserted value
fun <S: CharSequence> testInput(input: S?) { assertTrue(assertNotNull(input).all { it.isDigit() }) } S!!
S!! = S & Any
New type inference: summary
- new features are supported
- will be used by default in the future
- you can go and try it out
Have a nice Kotlin!
#kotlinconf18