Svetlana Isakova
@sveta_isakova
New Type Inference
& Related Language Features
Slide 2
Slide 2 text
Agenda
Experimental features
Contracts
New type inference
Slide 3
Slide 3 text
Experimental
features
Slide 4
Slide 4 text
Experimental features
Our goal: to let new features be tried
by early adopters as soon as possible
Slide 5
Slide 5 text
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
Slide 6
Slide 6 text
Automatic migration
Slide 7
Slide 7 text
Experimental Language Features
• you need to explicitly opt in at the call site
to use experimental features
kotlin { experimental { coroutines 'enable' } }
Slide 8
Slide 8 text
Experimental API for Libraries
• can be publicly released as a part of the library
• may break at any moment
Slide 9
Slide 9 text
@ShinyNewAPI
class Foo {
...
}
Experimental API
@Experimental
annotation class ShinyNewAPI
You can mark your shiny new class or function as experimental
Slide 10
Slide 10 text
Using experimental API
@UseExperimental(ShinyNewAPI::class)
fun doSomethingImportant() {
val foo = Foo()
...
}
Slide 11
Slide 11 text
• feedback loop for new features and API
Experimental: Summary
Slide 12
Slide 12 text
Contracts
Slide 13
Slide 13 text
Changes in standard library
Slide 14
Slide 14 text
inline fun run(block: () -> R): R = block()
Changes in standard library
Slide 15
Slide 15 text
inline fun run(block: () -> R): R = block()
inline fun run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
Changes in standard library
Slide 16
Slide 16 text
Changes in standard library
inline fun run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
inline fun run(block: () -> R): R = block()
Slide 17
Slide 17 text
We know something about run,
which the compiler doesn’t
val answer: Int
run {
answer = 42
}
println(answer)
Slide 18
Slide 18 text
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
Slide 19
Slide 19 text
val s: String? = ""
if (!s.isNullOrEmpty()) {
s.first()
}
We know something about isNullOrEmpty,
which the compiler doesn’t
Slide 20
Slide 20 text
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
Slide 21
Slide 21 text
Kotlin Contracts
…allow to share extra information
about code semantics with the compiler
Slide 22
Slide 22 text
Variable initialization inside run
val answer: Int
run {
answer = 42
}
println(answer)
Slide 23
Slide 23 text
Variable initialization inside run
val answer: Int
run {
answer = 42
}
println(answer)
✓
Slide 24
Slide 24 text
val s: String? = ""
if (!s.isNullOrEmpty()) {
s.first()
}
Making smart casts even smarter
Slide 25
Slide 25 text
val s: String? = ""
if (!s.isNullOrEmpty()) {
s.first()
}
Making smart casts even smarter
✓
Slide 26
Slide 26 text
inline fun run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
Contract: block lambda will be always called once
Slide 27
Slide 27 text
Contract for calling inlined lambda in-place
run, let, with, apply, also
takeIf, takeUnless, synchronized
Slide 28
Slide 28 text
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
Slide 29
Slide 29 text
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()
}
✓
Slide 30
Slide 30 text
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
Slide 31
Slide 31 text
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
Slide 32
Slide 32 text
Contract: if the function returns a given value,
a condition is satisfied
isNullOrEmpty, isNullOrBlank
kotlin.test: assertTrue, assertFalse, assertNotNull
check, checkNotNull, require, requireNotNull
Slide 33
Slide 33 text
Kotlin Contract
extra information by developer
&
compiler uses this information for code analysis
experimental
Slide 34
Slide 34 text
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
Slide 35
Slide 35 text
Why can’t compiler just implicitly
infer such information?
Slide 36
Slide 36 text
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
Slide 37
Slide 37 text
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
Slide 38
Slide 38 text
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() })
}
Slide 39
Slide 39 text
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() })
}
Slide 40
Slide 40 text
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() })
}
Slide 41
Slide 41 text
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() })
}
Slide 42
Slide 42 text
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() })
}
Slide 43
Slide 43 text
• handy functions (run, isEmptyOrNull)
are even more useful
• contract DSL will change
• you can go and try it out
Contracts: Summary
Slide 44
Slide 44 text
New type
inference
Slide 45
Slide 45 text
New type inference
better and more powerful type inference
new features are supported
Slide 46
Slide 46 text
kotlin { experimental { newInference 'enable' } }
Might be turned on in Kotlin 1.3
Slide 47
Slide 47 text
Kotlin libraries
• Libraries should specify return types for public API
• Overloaded functions must do the same thing
Slide 48
Slide 48 text
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”
Slide 49
Slide 49 text
New type inference
• SAM conversions for Kotlin functions
• better inference for builders
• better inference for call chains
• better inference for intersection types
Slide 50
Slide 50 text
SAM conversions
for Kotlin functions
Slide 51
Slide 51 text
fun handleInput(handler: Action) { ... }
SAM conversions for Kotlin functions
public interface Action {
void execute(T target);
}
Slide 52
Slide 52 text
fun handleInput(handler: Action) { ... }
SAM conversions for Kotlin functions
public interface Action {
void execute(T target);
}
You can pass a lambda as an argument
when a Java SAM-interface is expected:
handleInput { println(it) }
Slide 53
Slide 53 text
Support for several SAM arguments
Old inference:
observable.zipWith(anotherObservable,
BiFunction { x, y -> x + y })
Slide 54
Slide 54 text
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
Slide 55
Slide 55 text
Support for several SAM arguments
Old inference:
observable.zipWith(anotherObservable,
BiFunction { x, y -> x + y })
observable.zipWith(anotherObservable) { x, y -> x + y }
New inference:
Slide 56
Slide 56 text
Support for several SAM arguments
Old inference:
observable.zipWith(anotherObservable,
BiFunction { x, y -> x + y })
observable.zipWith(anotherObservable) { x, y -> x + y }
New inference:
✓
Slide 57
Slide 57 text
Builder inference
Slide 58
Slide 58 text
Inference for sequence
val seq = sequence {
yield(42)
}
Slide 59
Slide 59 text
Inference for sequence
val seq = sequence {
yield(42)
}
: Sequence
Slide 60
Slide 60 text
Inference for regular lambdas
people.map { person ->
println("Processed: ${person.name}")
person.age
}
Result type may depend on lambda return type
Slide 61
Slide 61 text
Inference for regular lambdas
people.map { person ->
println("Processed: ${person.name}")
person.age
}
Int
Result type may depend on lambda return type
Slide 62
Slide 62 text
Inference for regular lambdas
people.map { person ->
println("Processed: ${person.name}")
person.age
} List
Int
Result type may depend on lambda return type
Slide 63
Slide 63 text
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
Slide 64
Slide 64 text
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
Slide 65
Slide 65 text
Builder inference
automatically works for sequence in 1.3
opt in to use that for your functions
Slide 66
Slide 66 text
Using @BuilderInference for your function
fun buildList(
@BuilderInference init: MutableList.() -> Unit
): List {
return mutableListOf().apply(init)
}
val list = buildList {
add(cat)
add(dog)
}
: List
Experimental
Slide 67
Slide 67 text
Inference for call chains
Slide 68
Slide 68 text
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
Slide 69
Slide 69 text
Inference for call chains
fun createMap() = MapBuilder()
.put("answer", 42)
.build()
: Map
Old inference:
One call is analyzed at a time
New inference:
A call chain is analyzed
Slide 70
Slide 70 text
Intersection types
Slide 71
Slide 71 text
Better inference for intersection types
interface Drownable
interface Throwable
fun throwIntoRiver(thing: T)
where T : Drownable, T : Throwable {
println("Bye, $thing")
}
Slide 72
Slide 72 text
Better inference for intersection types
interface Drownable
interface Throwable
fun throwIntoRiver(thing: T)
where T : Drownable, T : Throwable {
println("Bye, $thing")
}
if (something is Drownable && something is Throwable) {
throwIntoRiver(something)
}
Slide 73
Slide 73 text
Drownable & Throwable
Better inference for intersection types
interface Drownable
interface Throwable
fun throwIntoRiver(thing: T)
where T : Drownable, T : Throwable {
println("Bye, $thing")
}
if (something is Drownable && something is Throwable) {
throwIntoRiver(something)
}
Slide 74
Slide 74 text
Intersection type to denote
not-nullable generic type
T!! = T & Any
Slide 75
Slide 75 text
Proper type for T!!
fun describe(x: T) {
println(x!!::class.simpleName)
}
Slide 76
Slide 76 text
Proper type for T!!
fun describe(x: T) {
println(x!!::class.simpleName)
}
T!!
Slide 77
Slide 77 text
Proper type for T!!
fun describe(x: T) {
println(x!!::class.simpleName)
}
T!!
T!!
fun describe(x: T) {
if (x != null) {
println(x::class.simpleName)
}
}
Slide 78
Slide 78 text
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() })
}
Slide 79
Slide 79 text
fun testInput(input: String?) {
assertTrue(assertNotNull(input).all { it.isDigit() })
}
Improving assertNotNull:
return asserted value
Slide 80
Slide 80 text
fun 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
Slide 81
Slide 81 text
Improving assertNotNull:
return asserted value
fun testInput(input: S?) {
assertTrue(assertNotNull(input).all { it.isDigit() })
}
Slide 82
Slide 82 text
Improving assertNotNull:
return asserted value
fun testInput(input: S?) {
assertTrue(assertNotNull(input).all { it.isDigit() })
}
S!!
S!! = S & Any
Slide 83
Slide 83 text
New type inference: summary
• new features are supported
• will be used by default in the future
• you can go and try it out