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

Demystifying Co, Contra, in Kotlin

Demystifying Co, Contra, in Kotlin

This presentation is the backup compilation of materials used to create my video on YouTube about variance, convariance, contravariance and invariance. I aproach the topic in a different way than in the slides, but both perspectives are important to understand covariance and contravariance which are topics that can be quite convoluted. The video is located here: https://www.youtube.com/watch?v=D50Cluc2Vp4. Should you have any questions regarding this presentation please leave a comment.

More Decks by João Filipe Sabino Esperancinha

Transcript

  1. Who am I? • Software Engineer for 10+ years •

    Java, Kotlin, Scala, Groovy, Clojure • Studied at ISEL Lisboa in Computer Science and Telecom Engineering • Spring Professional 2020 • OCP11 • Kong Champion
  2. Variances The ability to establish hierarchies between complex types. Example:

    is a list of credit cards the same as a list of cards and vice-versa? Sounds logic that a list of credit cards is also a list of cards, but is it?
  3. Covariance abstract class DrinksService<in DRINK : Drink, out BOX :

    Box> { protected val database by lazy { HashMap<UUID, Drink>() } fun sendDrink(drink: DRINK) = run { database[UUID.randomUUID()] = drink } abstract fun getBox(): BOX } open class Box open class CardboardBox : Box() open class PlasticBox : Box() DrinkService is covariant to Box!
  4. Covariance val coldDrinksBoxService: DrinksService<ColdDrink, Box> = object: DrinksService<ColdDrink, FamilyBox>() {

    override fun getBox(): FamilyBox { TODO("Not yet implemented") } } DrinkService is covariant to Box! Will this compile? Yes! Because of covariance DrinksService<ColdDrink, FamilyBox> Is seen as a subclass of: DrinksService<ColdDrink, Box> 👍
  5. Covariance val coldDrinksFamilyBoxService: DrinksService<ColdDrink, FamilyBox> = object: DrinksService<ColdDrink, Box>() {

    override fun getBox(): Box { TODO("Not yet implemented") } } DrinkService is covariant to Box! Will this compile? No! Because of covariance DrinksService<ColdDrink, Box> Is NOT seen as a subclass of: DrinksService<ColdDrink, FamilyBox> 👎
  6. Covariance abstract class DrinksService<in DRINK : Drink, out BOX :

    Box> { protected val database by lazy { HashMap<UUID, Drink>() } fun sendDrink(drink: DRINK) = run { database[UUID.randomUUID()] = drink } abstract fun getBox(): BOX } open class Drink open class WarmDrink : Drink() open class ColdDrink : Drink() DrinkService is contravariant to Drink!
  7. Contravariance val drinksFamilyBoxService: DrinksService<ColdDrink, FamilyBox> = object : DrinksService<Drink, FamilyBox>()

    { override fun getBox(): FamilyBox { TODO("Not yet implemented") } } DrinkService is contravariant to Drink! Will this compile? Yes! Because of contravariance DrinksService<ColdDrink, FamilyBox> Is seen as a subclass of: DrinksService<Drink, FamilyBox> 👍
  8. Covariance val coldDrinksFamilyBoxService: DrinksService<Drink, FamilyBox> = object : DrinksService<ColdDrink, FamilyBox>()

    { override fun getBox(): FamilyBox { TODO("Not yet implemented") } } DrinkService is contravariant to Drink! Will this compile? No! Because of contravariance DrinksService<Drink, FamilyBox> Is not seen as a subclass of: DrinksService<ColdDrink, FamilyBox> 👎
  9. Contravariance can be very counterintuitive to understand but essentially in

    common words, it means that the combination with a certain complex type will still result in a composed generic type in this case a DrinksService<in DRINK : Drink, out BOX : Box>. The idea is that the resulting composed type will have its own hierarchy and that can move along or against the original hierarchy of its individual contained types. And so Drinks service is covariant to BOX, but contravariant to DRINK. Therefore naturally if covariant, then the type makes only sense to use as an output and thus the modifier out. In the same if contravariant, then it only make sense to use that generic type as input and thus in.
  10. In Java - The Drinks Service public class DrinksServiceJava<DRINK extends

    Drink, BOX extends Box> { private final Map<UUID, DRINK> database = new HashMap<>(); public void sendDrink(DRINK drink) { database.put(UUID.randomUUID(), drink); } @SuppressWarnings("unused") public BOX getBox(Function<DRINK,BOX> drinks) { return drinks.apply(database.remove(database.keySet().stream().findFirst().orElseThrow())); } }
  11. In Java - Service initialization DrinksServiceJava<? super WarmDrink, ? extends

    Box> drinksFamilyBoxService = new DrinksServiceJava<Drink, FamilyBox>(); DrinkService is contravariant to Drink! DrinkService is covariant to Box! Different Language Different Syntax The same stuff
  12. Resources • Source Repository ◦ https://github.com/jesperancinha/asnsei-the-right-waf • Location Directory: ◦

    https://github.com/jesperancinha/asnsei-the-right-waf/tree/main/demo-projects/drinks-manager Use git clone from the command prompt to download the full code base: > git clone https://github.com/jesperancinha/asnsei-the-right-waf.git You’ll be prompted for a username and password which should be your github account. > cd /demo-projects/drinks-manager The easy way: > make b > make run The manual way: > gradle build > ./gradlew run
  13. About me • Homepage - https://joaofilipesabinoesperancinha.nl • LinkedIn - https://www.linkedin.com/in/joaoesperancinha/

    • YouTube - JESPROTECH ▪ https://www.youtube.com/channel/UCzS_JK7QsZ7ZH-zTc5kBX_g ▪ https://www.youtube.com/@jesprotech • Bluesky - https://bsky.app/profile/jesperancinha.bsky.social • Mastodon - https://masto.ai/@jesperancinha • GitHub - https://github.com/jesperancinha • Hackernoon - https://hackernoon.com/u/jesperancinha • DevTO - https://dev.to/jofisaes • Medium - https://medium.com/@jofisaes