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

Presentation - The Ins and Outs of Generics in Kotlin by Erik Colban

SD Kotlin
September 05, 2018

Presentation - The Ins and Outs of Generics in Kotlin by Erik Colban

Presentation slides for SD Kotlin's September 2018 meeting.

SD Kotlin

September 05, 2018
Tweet

More Decks by SD Kotlin

Other Decks in Programming

Transcript

  1. Outline • Using Kotlin’s built-in generic classes and functions •

    Variance ◦ Covariance, contravariance, invariance • Declaring generic functions, properties, and classes • Variance ◦ Type projections ◦ Use-site vs declaration-site variance specification • Type erasure ◦ What it is ◦ How to get around it ◦ *- projections
  2. Using Kotlin’s built-in generic classes and function • Possible ◦

    val list: List<Int> = listOf<Int>(12, 3, 1007) ◦ val list = listOf<Int>(12, 3, 1007) ◦ val list: List<Int> = listOf(12, 3, 1007) ◦ val list = listOf(12, 3, 1007) ◦ val list: List<Number> = listOf<Int>(12, 3, 1007) • Not possible ◦ val array: Array<Number> = arrayOf<Int>(12, 3, 1007) • Note: In Java: ◦ Number[] array = new Integer[]{12, 3, 1007}; ◦ Big mistake! ▪ array[0] = 12.3; ⇒ java.lang.ArrayStoreException
  3. Variance Covariance: If C<T> is a generic type with type

    parameter T and U is a subtype of T, then C<U> is a subtype of C<T> • U subtype of T ⇒ C<U> subtype of C<T> • Example: List<Int> is a subtype of List<Number> because Int is a subtype of Number. • Applies to types that are “producers”, or a “source” of T • T only appears only in “out” position, i.e., the return type of a function ◦ Example: getters • T is never in “in” position, i.e. the type of a function argument.
  4. Variance Contra-variance: If C<T> is a generic type with type

    parameter T and U is a subtype of T, then C<T> is a subtype of C<U> • U subtype of T ⇒ C<T> subtype of C<U> • Example: Function1<Number, Int> is a subtype of Function1<Int, Int> because Int is a subtype of Number. • Applies to types that are “consumers” of T • T only appears only in “in” position, i.e., the type of a function argument • T is never in “out” position, i.e. the return type of a function.
  5. Variance Invariance: If C<T> is a subtype of C<U>, then

    T = U • Example: Array<T> is invariant in T • T appears in both “in position” and “out position” • Type is both a producer and consumer of T To remember: Lambdas are contra-variant in their argument types and covariant in their return type
  6. Variance Rationale Liskov’s substitution principle: “You can substitute a subtype

    for a super-type.” • “Annie get your gun”-principle ◦ Subtype to super-type: “Anything you can do, I can to better. I can do anything better than you!” • Example: If anything a List<Number> produces is OK, then anything a List<Integer> produces is OK too. • Example: Any input to a Function1<Int, Int> can be input to a Function1<Number, Int> too. • Example: An Array<Int> is better than an Array<Number> in the same sense that a List<Int> is better than a List<Number>, but an Array<Number> can hold anything an Array<Int> can hold. So neither is better than the other.
  7. Scenario Bob arrives late to his hotel. Hungry, but not

    eager to go out to a restaurant, he asks the hotel clerk if there is a vending machine (VM) in the hotel where he can buy a chocolate bar. He also says he only has a few dollar bills to pay with, so the vending machine must accept dollar bills as payment. The hotel clerk gives Bob directions to the vending machine, and Bob heads to the VM. After seeing the VM, Bob is furious! He stomps back to the reception and complains: “I specifically asked for a VM that accepts dollar bills as payment, but that machine accepts coins, banknotes, credit cards, and what have you! Moreover, it has no selection for chocolate bar! KitKat, Snickers, chips and peanuts, yes, but chocolate bar? Nooo!” “Is there a problem?”, asks the bewildered hotel clerk.
  8. Code Examples • See https://github.com/ecolban/KotlinGenerics • This project uses a

    library: <root url="jar://$USER_HOME$/jars/robot.jar!/" /> (see .idea/libraries/robot.xml). Copy this jar from https://github.com/jointheleague/Robot/blob/master/jar/robot.jar
  9. Type Projections /** * All occurrences of T in Array<in

    T> that are in "out position" are replaced by Any? */ fun <T : Number> getFromInProjection(a: Array<in T>) { val b = a[0] } /** * All occurrences of T in Array<out T> that are in "in position" are replaced by Nothing */ fun <T : Number> insertIntoOutProjection(a: Array<out T>, b: T) { a[0] = b ⇒ Error!! (Would only work if b were of type Nothing :-) } • “T in in position” = “T is an argument type” • “T in out position” = “T is a return type”
  10. Use-site vs Declaration-site Variance • Declaration-site variance ◦ Specifies a

    subtype relationship between generic types ◦ Example: class GenericRobot<in T> declares that GenericRobot<RobotCommand> is a subtype of GenericRobot<PrimitiveCommand> (because RobotCommand is a supertype of PrimitiveCommand). ◦ “Once for all” declaration • Use-site variance ◦ Specifies which types can be assigned to a variable or function argument, without there being a subtype relationship between them ◦ Example: fun <T : RobotCommand> execute(robot: GenericRobot<in T>, command: T) {...} declares that when T is bound to PrimitiveCommand, robot can be of type GenericRobot<RobotCommand> ◦ Uses type projections (in this example, replaces all occurrences of T in out position in GenericRobot with Any?)
  11. Type Erasure • Type checking and inference happens at compile

    time ◦ Example: Compiler verifies and “knows” that an instance of List<String> only contains instances of String and does not contain null. ◦ Compiler assigns types to type parameters • During runtime, the information about which types have been assigned to type parameters is lost. • See code examples