Slide 1

Slide 1 text

The Kotlin Type Hierarchy from top to bottom (including the ins and outs of generics) Nat Pryce @natpryce github.com/npryce speakerdeck.com/npryce

Slide 2

Slide 2 text

???

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

JVM heap CAFE BABE ... foo() { ... } Types classify syntactic expressions, not runtime values fun f() { ... } 101001 011001 011100 1100... kotlinc class class Type Checker

Slide 5

Slide 5 text

Types help you avoid common runtime errors https://rollbar.com/blog/top-10-javascript-errors/

Slide 6

Slide 6 text

The same errors occur on the JVM due to reflection https://stackoverflow.com/questions/tagged/spring?sort=frequent

Slide 7

Slide 7 text

There’s no trade off between type safety & clarity resources { journalCss methods { GET { _, journalId -> journalId.toPCode().asResultOr { JournalNotFound(journalId) } .flatMap { pCode -> storage.style(pCode) } .map { Response(OK).body(it.toCSS()).contentType(CSS_UTF_8).cacheable().allowedFromAnyOrigin() } .orElse { reportError(it, monitor) } } } journalLogo methods { GET { _, (journalId, format) -> journalId.toPCode().asResultOr { JournalNotFound(journalId) } .flatMap { pCode -> storage.logo(pCode, format) } .map { logoUri -> Response(FOUND).location(logoUri).cacheable() } .orElse { reportError(it, monitor) } } } partnerLogo methods { GET { _, partnerCode -> storage.partnerLogo(partnerCode) .onEachFailure { monitor.notify(MissingBrandingEvent(it)) } .flatMapFailure { storage.defaultPartnerLogo() } .map { logoBytes -> Response(OK).body(logoBytes).contentType(PNG).cacheable() } .orElse { reportError(it, monitor) } } } } http4k.org, github.com/npryce/krouton, github.com/npryce/result4k

Slide 8

Slide 8 text

Kotlin’s type hierarchy from top to bottom

Slide 9

Slide 9 text

Kotlin has built-in and user-defined types Logical Integral Floating Point Structured And these... Boolean Byte Float Array Any Char Double String Nothing Int Function types Unit Long Classes, interfaces and enums create user-defined types Function types, the “nullable” operator and generics compose types The Kotlin language defines some built-in types:

Slide 10

Slide 10 text

Kotlin types are related by subtyping Here, Apple is a subtype of Fruit, Fruit is a supertype of Apple. Can be read as "An Apple is a Fruit". A subtype can be passed anywhere that expects its supertype. I can pass an Apple to code that expects a Fruit But not vice versa: I cannot pass arbitrary Fruit to code that expects only Apples Subtyping transitive An Apple is a Fruit, a Pippin is an Apple, therefore a Pippin is a Fruit

Slide 11

Slide 11 text

Any is the ultimate supertype of all classes Any is the equivalent of Java's Object type Except: its values are never null

Slide 12

Slide 12 text

Any is also the ultimate supertype of interfaces Note: the relationships between interface types is different to the relationships between classes exposed by reflection.

Slide 13

Slide 13 text

Any has a few general operations Casting to a useful type sidesteps the type checker Beware of type erasure: not all casts can be fully checked at runtime Avoid unsafe casts by ● Programming to interfaces ● Sealed class hierarchies The compiler can’t help you if you use Any

Slide 14

Slide 14 text

fun example() { println("Hello, KotlinConf") } Functions that only have side effects return Unit fun example(): Unit { println("Hello, KotlinConf") return Unit } package kotlin public object Unit { override fun toString() = "kotlin.Unit" } =

Slide 15

Slide 15 text

Evaluating an expression of type Unit produces the singleton value Unit Evaluating an expression of type Nothing never produces a value at all. Expressions that do not produce a value have type Nothing

Slide 16

Slide 16 text

We can include control flow in expressions Throw: val fruitPerGlass : Int = if (glassesSold > 0) orangesUsed/glassesSold else throw NoGlassesSold() Or even return: fun generateReport(orangesUsed: Int, glassesSold: Int): String { val fruitPerGlass : Int = if (glassesSold > 0) orangesUsed/glassesSold else return "no glasses sold" ... }

Slide 17

Slide 17 text

We can write our own control flow statements The compiler's unreachable code analysis will understand them. inline fun forever(block: ()->Unit): Nothing { while(true) { block() } } forever { val m = receiveMessage() dispatchMessage(m) } println("finished") Unreachable code

Slide 18

Slide 18 text

There is no way to declare that an expression cannot fail Just admitting the truth: a Kotlin program can, at any time: ● exhaust memory ● be terminated ● receive an unexpected signal ● have it’s thread interrupted ● overflow the stack ● have an incorrect classpath that causes a link error ● fail to load a class because of a hardware error ● ...

Slide 19

Slide 19 text

Nulls and Nullability

Slide 20

Slide 20 text

Nullable types form a parallel subtype hierarchy

Slide 21

Slide 21 text

Nullability is itself a subtype relationship

Slide 22

Slide 22 text

Beware: Any does not mean "any value"

Slide 23

Slide 23 text

Unit can be nullable — it is not a special case like void

Slide 24

Slide 24 text

Nothing? is the subtype of all nullable types

Slide 25

Slide 25 text

val someJson = objectOf( "x" of 10, "y" of null ) The null literal can cause overload ambiguity Error: overload ambiguity private val factory = JsonNodeFactory.instance fun objectOf(vararg properties: Pair) = factory.objectNode().apply { setAll(properties.toMap()) } infix fun String.of(propertyValue: Int?) = Pair(this, factory.numberNode(propertyValue)) infix fun String.of(propertyValue: String?) = Pair(this, factory.textNode(propertyValue))

Slide 26

Slide 26 text

val someJson = objectOf( "x" of 10, "y" of null ) Avoid ambiguity with explicit overloads for null literal private val factory = JsonNodeFactory.instance fun objectOf(vararg properties: Pair) = factory.objectNode().apply { setAll(properties.toMap()) } infix fun String.of(propertyValue: Int?) = Pair(this, factory.numberNode(propertyValue)) infix fun String.of(propertyValue: String?) = Pair(this, factory.textNode(propertyValue)) infix fun String.of(propertyValue: Nothing?) = Pair(this, factory.nullNode())

Slide 27

Slide 27 text

A generic type parameter T (no “?”) is nullable Type variables are nullable, even though they do not have a '?' suffix! E.g. the list created by this function may contain null. fun listOf(vararg items: T): List = ... Constrain a type parameter to be a subtype of Any to declare it non-null E.g. the following function guarantees its result has no null elements fun Iterable.filterNotNull(): List = ...

Slide 28

Slide 28 text

The ins and outs of generics

Slide 29

Slide 29 text

A generic is not a type, but a "function on types" interface Box { fun put(item: ITEM) fun take(): ITEM } Box Box Applying a generic to a type (or types) produces a type

Slide 30

Slide 30 text

By default, generic types do not introduce subtyping interface Box { fun put(item: ITEM) fun take(): ITEM } Box Box

Slide 31

Slide 31 text

Use "output" variance to identify what is returned interface Supplier { fun receive(): T } Fruit Supplier Oranges Supplier

Slide 32

Slide 32 text

Use "input" variance to identify what is passed in interface Juicer { fun juice(solid: T): Liquid } Juicer Juicer

Slide 33

Slide 33 text

Beware: input/output variance is not inferred … except for function types It's easy to make your generic types too restrictive. Be careful if publishing libraries. interface Hopper { fun takeOne(): T } class AutomaticJuicer(val source: Hopper) val apples: Hopper = ... val juicer = AutomaticJuicer(apples)

Slide 34

Slide 34 text

Separate types for read, write & read/write interface Store { fun put(item: T) } interface Source { fun take(): T } interface Box: Store, Source Lots of examples in the standard library. E.g. List / MutableList

Slide 35

Slide 35 text

Subtyping!

Slide 36

Slide 36 text

The End Nat Pryce natpryce.com @natpryce github.com/npryce speakerdeck.com/npryce Libraries cited in this talk: http4k.org github.com/npryce/hamkrest github.com/npryce/krouton github.com/npryce/result4k Diagrams generated by PlantUML