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.
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Demystifying Co, Contra, In Kotlin modifier keywords.pptx
1. Demystifying Co, Contra, In
Kotlin
A Lightning Presentation (support slides 2023)
By João Esperancinha (2024/03/15)
2. 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
3. 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?
5. 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!
6. 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>
👍
7. 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>
👎
9. 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!
10. 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>
👍
11. 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>
👎
12. 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.
13. In Java
● Same implementation
● Same rules
● A bit more code
14. 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()));
}
}
15. 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
17. 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