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
Using Kotlin’s built-in generic classes and function ● Possible ○ val list: List = listOf(12, 3, 1007) ○ val list = listOf(12, 3, 1007) ○ val list: List = listOf(12, 3, 1007) ○ val list = listOf(12, 3, 1007) ○ val list: List = listOf(12, 3, 1007) ● Not possible ○ val array: Array = arrayOf(12, 3, 1007) ● Note: In Java: ○ Number[] array = new Integer[]{12, 3, 1007}; ○ Big mistake! ■ array[0] = 12.3; ⇒ java.lang.ArrayStoreException
Variance Covariance: If C is a generic type with type parameter T and U is a subtype of T, then C is a subtype of C ● U subtype of T ⇒ C subtype of C ● Example: List is a subtype of List 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.
Variance Contra-variance: If C is a generic type with type parameter T and U is a subtype of T, then C is a subtype of C ● U subtype of T ⇒ C subtype of C ● Example: Function1 is a subtype of Function1 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.
Variance Invariance: If C is a subtype of C, then T = U ● Example: Array 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
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 produces is OK, then anything a List produces is OK too. ● Example: Any input to a Function1 can be input to a Function1 too. ● Example: An Array is better than an Array in the same sense that a List is better than a List, but an Array can hold anything an Array can hold. So neither is better than the other.
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.
Code Examples ● See https://github.com/ecolban/KotlinGenerics ● This project uses a library: (see .idea/libraries/robot.xml). Copy this jar from https://github.com/jointheleague/Robot/blob/master/jar/robot.jar
Type Projections /** * All occurrences of T in Array that are in "out position" are replaced by Any? */ fun getFromInProjection(a: Array) { val b = a[0] } /** * All occurrences of T in Array that are in "in position" are replaced by Nothing */ fun insertIntoOutProjection(a: Array, 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”
Use-site vs Declaration-site Variance ● Declaration-site variance ○ Specifies a subtype relationship between generic types ○ Example: class GenericRobot declares that GenericRobot is a subtype of GenericRobot (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 execute(robot: GenericRobot, command: T) {...} declares that when T is bound to PrimitiveCommand, robot can be of type GenericRobot ○ Uses type projections (in this example, replaces all occurrences of T in out position in GenericRobot with Any?)
Type Erasure ● Type checking and inference happens at compile time ○ Example: Compiler verifies and “knows” that an instance of List 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