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

The Kotlin Type Hierarchy From Top to Bottom

Nat Pryce
October 04, 2018

The Kotlin Type Hierarchy From Top to Bottom

The Kotlin Type Hierarchy, from top to bottom (including the ins and outs of generics)

Coming from Java, Kotlin's type system can appear quite daunting: nullable types, Unit, Nothing, Any, variance specifiers, function types, and more. But don't panic! Kotlin's type hierarchy is actually simple – it has very few rules to learn and those rules combine together consistently and predictably. Thanks to those rules, Kotlin useful language features, such as null safety and unreachable code analysis, do not rely on special cases that you must memorise, or ad-hoc checks in the compiler and IDE that stop working when your code gets too complicated.

In this talk Nat will take you on a tour of Kotlin's type hierarchy and demonstrate how to use it to model your application domains, using example code taken from real projects.

Presented at KotlinConf 2018, Amsterdam.

Video: https://www.youtube.com/watch?v=juFkdMv4B9s

Nat Pryce

October 04, 2018
Tweet

More Decks by Nat Pryce

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  5. 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

    View full-size slide

  6. Kotlin’s type hierarchy from top to bottom

    View full-size slide

  7. 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:

    View full-size slide

  8. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  11. 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

    View full-size slide

  12. 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"
    }
    =

    View full-size slide

  13. 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

    View full-size slide

  14. 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"
    ...
    }

    View full-size slide

  15. 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

    View full-size slide

  16. 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
    ● ...

    View full-size slide

  17. Nulls and Nullability

    View full-size slide

  18. Nullable types form a parallel subtype hierarchy

    View full-size slide

  19. Nullability is itself a subtype relationship

    View full-size slide

  20. Beware: Any does not mean "any value"

    View full-size slide

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

    View full-size slide

  22. Nothing? is the subtype of all nullable types

    View full-size slide

  23. 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))

    View full-size slide

  24. 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())

    View full-size slide

  25. 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 = ...

    View full-size slide

  26. The ins and outs of generics

    View full-size slide

  27. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. 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)

    View full-size slide

  32. 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

    View full-size slide

  33. 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

    View full-size slide