avatarGabriel Shanahan

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

5162

Abstract

of return value</li><li>Assigning a function with a more specific (i.e. subtype) return value and the same type of argument</li><li>Assigning a function with a more general (i.e. supertype) return value and the same type of argument</li></ul><p id="8d8f">Let’s go through them, one by one.</p><h1 id="7741">Is a function with a more general argument assignable?</h1><p id="c5c6"><b>Yes.</b></p> <figure id="633d"> <div> <div> <img class="ratio" src="http://placehold.it/16x9"> <iframe class="" src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fpl.kotl.in%2FwN_kjY_pu&amp;display_name=Kotlin+Playground&amp;url=https%3A%2F%2Fpl.kotl.in%2FwN_kjY_pu&amp;image=https%3A%2F%2Fplay.kotlinlang.org%2Fassets%2Fog-image.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=kotl" allowfullscreen="" frameborder="0" height="300" width="800"> </div> </div> </figure></iframe></div></div></figure><p id="e404">This works, because <code>::f1</code> accepts an <code>Int</code> and an <code>Int</code> is a <code>Number</code>, so <code>::assignedFun</code> accepts it too. In other words, if the return values are compatible, any function accepting a supertype argument is assignable.</p><p id="d6c5">Intuitively, any function that can compute the same result from less information (i.e. a supertype instance) can be used — if we have an algorithm that works for <code>Number</code>, it will definitely work for <code>Int</code>.</p><p id="104e">This means that even though <code>Number</code> is a <b>supertype</b> of <code>Int</code>, <code>(Number) -> CharSequence</code> is a <b>subtype</b> of <code>(Int) -> CharSequence</code>, because you can assign the former to the latter.</p><h1 id="9cdd">Is a function with a more specific argument assignable?</h1><p id="0528"><b>No.</b></p> <figure id="2fb2"> <div> <div> <img class="ratio" src="http://placehold.it/16x9"> <iframe class="" src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fpl.kotl.in%2F8k6XqOFc8&amp;display_name=Kotlin+Playground&amp;url=https%3A%2F%2Fpl.kotl.in%2F8k6XqOFc8&amp;image=https%3A%2F%2Fplay.kotlinlang.org%2Fassets%2Fog-image.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=kotl" allowfullscreen="" frameborder="0" height="300" width="800"> </div> </div> </figure></iframe></div></div></figure><p id="7c55">Intuitively, we can’t pretend we can create a <code>CharSequence</code> out of anything when we only know how to create a <code>CharSequence</code> out of <code>Number</code>s. If we have an algorithm that works for <code>Number</code>, it sure as shit doesn’t mean we have an algorithm that works for <code>Any</code>.</p><p id="ef6f">This means that <code>(Number) -> CharSequence</code> is not a subtype of <code>(Any) -> CharSequence</code>. It is in fact its super-type, as we saw above, which is another reason why the answer is ‘no’ — a type cannot be a super-type and a subtype of another type at the same time!</p><h1 id="5cac">Is a function with a more specific return value assignable?</h1><p id="1096"><b>Yes.</b></p> <figure id="bc93"> <div> <div> <img class="ratio" src="http://placehold.it/16x9"> <iframe class="" src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fpl.kotl.in%2FZNjVh7zvI&amp;display_name=Kotlin+Playground&amp;url=https%3A%2F%2Fpl.kotl.in%2FZNjVh7zvI&amp;image=https%3A%2F%2Fplay.kotlinlang.org%2Fassets%2Fog-image.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=kotl" allowfullscreen="" frameborder="0" height="300" width="800"> </div> </div> </figure></iframe></div></div></figure><p id="e2e7">This works, because <code>::assignedFun</code> returns a <code>CharSequence</code> and a <code>CharSequence</code> is an <code>Any</code>, so this conforms with <code>::f3</code>'s type. In other words, if the arguments are compatible, any function returning a subtype of the original's return value is assignable.</p><p id="423e">Intuitively, any function that can compute a more specific result (i.e. a subtype instance) from the same amount information can be used — if we have an algorithm that produces a <code>CharSequence</code>, it can definitely produce (actually, it <i>is</i> producing) an <code>Any</code>.</p><p id="36bd">This means that, since <code>CharsSequence</code> is a <b>subtype</b> of <code>Any</code>, <code>(Number) -> CharSequence</code> is a <b>subtype</b> of <code>(Number) -> Any</code>.</p><h1 id="528e">Is a function with a more general return value assignable?</h1><p id="5998"><b>No.</b></p> <figure id="4c62"> <div> <div> <img class="ratio" src="http://placehold.it/16x9"> <iframe class="" src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fpl.kotl.in%2FgelpeUCA-&amp;display_name=Kotlin+Playground&amp

Options

;url=https%3A%2F%2Fpl.kotl.in%2FgelpeUCA-&image=https%3A%2F%2Fplay.kotlinlang.org%2Fassets%2Fog-image.png&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&schema=kotl" allowfullscreen="" frameborder="0" height="300" width="800"> </div> </div> </figure></iframe></div></div></figure><p id="b337">Intuitively, we can’t pretend we know how to create <code>String</code>s when all we know is how to create <code>CharSequence</code>s. This means that, <code>(Number) -> CharSequence</code> is not a subtype of <code>(Number) -> String</code>. It is in fact its super-type, as we saw above, which, again, is another reason why the answer is ‘no’ — one cannot be both a subtype and the super-type of another type.</p><p id="91a3">We can see that, when we want to <i>substitute</i> one function for another, we can <b>vary</b> the types in opposite directions — types that go <b>in</b> can be any <b>super-type</b>, while types that come <b>out</b> can be any <b>subtype</b>. The words we used in this sentence — <i>in</i>, <i>out</i> — are no coincidence, and are the basis of technical jargon that will be discussed in the following chapter.</p><p id="b9d7">Also, notice that a problem arises when a type goes <i>both in and out</i>:</p> <figure id="9860"> <div> <div> <img class="ratio" src="http://placehold.it/16x9"> <iframe class="" src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fpl.kotl.in%2FYkAmi0VUQ&amp;display_name=Kotlin+Playground&amp;url=https%3A%2F%2Fpl.kotl.in%2FYkAmi0VUQ&amp;image=https%3A%2F%2Fplay.kotlinlang.org%2Fassets%2Fog-image.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=kotl" allowfullscreen="" frameborder="0" height="300" width="800"> </div> </div> </figure></iframe></div></div></figure><p id="8d47">This is <i>precisely</i> why problems arose when we changed <code>val</code> to <code>var</code> in <a href="https://readmedium.com/generic-variance-motivation-95565bc74f3c">the <code>Res</code>ult example of the previous chapter</a>. As long as the <code>value</code> property was <code>val</code>, there was no place in <code>Result</code> where <code>T</code> went <i>in — </i>it always went <i>out</i> (<code>val</code> only generates getters). However, once we changed it to <code>var</code>, we suddenly got both a getter (where <code>T</code> comes <i>out</i>) and a setter (where a <code>T</code> goes <i>in</i>). Same with <code>Comparable</code> — the type <code>T</code> goes <i>in</i> in the <code>compareTo</code> method, and <i>out</i> in the getter.</p><p id="2545"><b>And that is the rule</b> — when we write a generic construct where the generic variable goes <i>in</i> in some places, and <i>out</i> in others, it is <b>impossible<i> </i></b>to consistently translate sub-/super-type relationships between different <code>T</code>’s to sub-/super-type relationships between generic constructs involving <code>T</code>’s, without allowing runtime errors to happen. If the <code>T</code>’s only go <i>in,</i> or only go <i>out</i>, it’s <i>possible</i> to do that, but it’s not required, and doesn’t happen automatically. The <i>in</i> situation is called <i>contravariance</i>, the <i>out</i> situation is called <i>covariance, </i>and we’ll talk about them more in the following chapter.</p><p id="a9cd">Understand that all of these facts <b>follow directly from a single fundamental principle of OOP</b> — that a subtype is only assignable to itself or its supertypes. All of the above is completely general, and not bound to any single language. It arises implicitly with any implementation of generics, in every language that observes this simple OOP principle.</p><p id="81ca">Armed with this knowledge, let’s recap what variance is all about — it is about being able to determine which of the above scenarios is <i>permissible </i>for a certain generic construct. And the way you do that is by studying if the generic variable appears only in <i>in</i> positions, only in <i>out</i> positions, or a mix of both. That’s all there is to it.</p><p id="8ab9">The following chapter will talk about keywords in Kotlin (<code>in</code> and <code>out</code>) that you use when one of the above situations is permissible and you specifically want to allow it.</p><p id="a3ba">Go back to <a href="https://readmedium.com/generic-variance-motivation-95565bc74f3c">Generic Variance — Motivation</a>, jump to the <a href="https://readmedium.com/table-of-contents-c52573cfa291">Table of Contents</a>, or continue to <a href="https://readmedium.com/covariance-contravariance-invariance-e2af93bdbdb2">Covariance, Contravariance, Invariance</a>.</p><figure id="8ecd"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*biBSB579iezsNvEQ_NMLBg.png"><figcaption><a href="https://www.etnetera.cz/prace-u-nas?utm_source=medium&amp;utm_medium=GabrielShanahan&amp;utm_campaign=KotlinPrimer&amp;utm_content=join-our-team&amp;utm_term=KotlinPrimer#pozice">Join me in Etnetera</a></figcaption></figure></article></body>

Generic Variance — Fundamental Principles

Demystifying variance once and for all — explaining the fundamental principles of variance from the bottom up, using assignability of functions.

— — — — — — — — — — — — — — —

THE CURRENT VERSION OF THIS ARTICLE IS PUBLISHED HERE.

— — — — — — — — — — — — — — —

Tags: #FYI++

This article is part of the Kotlin Primer, an opinionated guide to the Kotlin language, which is indented to help facilitate Kotlin adoption inside Java-centric organizations. It was originally written as an organizational learning resource for Etnetera a.s. and I would like to express my sincere gratitude for their support.

It is recommended to read the Introduction before moving on. Check out the Table of Contents for all articles.

The basic question we posed in the previous article is this: Can we, and when can we, take code (a class, a function) that was generated (using generics) for a Dog type and use it in place of code generated for an Animal type? Can we vary the generic type? And if so, under what conditions? More technically, when there is a sub-/super-type relationship between some types, does it, and when can it, translate to some sub-/super-type relationship between generic constructs using those types?

This is the question I’ll be answering in this article. But in order to do so, we first need to understand something about generics.

In a sense, generics always generate functions

To see why this is so, let’s break down the only two things that can be generated via generics: classes, and functions.

(Generic) functions are, well, functions, so that part is obvious. (Generic) classes are collections of methods (which are functions) and properties, which, in Kotlin, are represented by a getter and possibly a setter — both of them functions.

Why is this important? Because, if we accept that generic constructs are simply instructions for generating a set of functions, to determine the situations when one such construct can be used in place of another, all we need to do is answer this: under what conditions can one function type be used in place of a different function type?

val f1: <SomeFunctionType> = <something>
// When can we do this?
val f2: <SomeOtherFunctionType> = f1

Once we’ve answered that, answering the same question for generic constructs is easy — one construct can be used in place of another if all the functions generated in one can be used in place of the corresponding functions of the other.

Take a minute to absorb that before reading on.

Assignability of functions

First, let’s get the obvious out of the way — obviously, all the types appearing as arguments in one function must be sub-or-super-types of the types of corresponding arguments in the other function. There is no way a String can be assigned to an Int, so there’s no way to assign a function accepting or returning an Int to a function accepting or returning a String. The same goes for return values. There is also no way to mix functions accepting a different number of arguments.

In other words, it makes no sense to study functions whose structures are different and whose corresponding argument/return value types are not related to one another via inheritance.

We also know that if the second function has exactly the same types as the first one, it is obviously assignable, so we'll also skip that. That leaves four different scenarios:

  • Assigning a function with a more general (i.e. supertype) argument and the same type of return value
  • Assigning a function with a more specific (i.e. subtype) argument and the same type of return value
  • Assigning a function with a more specific (i.e. subtype) return value and the same type of argument
  • Assigning a function with a more general (i.e. supertype) return value and the same type of argument

Let’s go through them, one by one.

Is a function with a more general argument assignable?

Yes.

This works, because ::f1 accepts an Int and an Int is a Number, so ::assignedFun accepts it too. In other words, if the return values are compatible, any function accepting a supertype argument is assignable.

Intuitively, any function that can compute the same result from less information (i.e. a supertype instance) can be used — if we have an algorithm that works for Number, it will definitely work for Int.

This means that even though Number is a supertype of Int, (Number) -> CharSequence is a subtype of (Int) -> CharSequence, because you can assign the former to the latter.

Is a function with a more specific argument assignable?

No.

Intuitively, we can’t pretend we can create a CharSequence out of anything when we only know how to create a CharSequence out of Numbers. If we have an algorithm that works for Number, it sure as shit doesn’t mean we have an algorithm that works for Any.

This means that (Number) -> CharSequence is not a subtype of (Any) -> CharSequence. It is in fact its super-type, as we saw above, which is another reason why the answer is ‘no’ — a type cannot be a super-type and a subtype of another type at the same time!

Is a function with a more specific return value assignable?

Yes.

This works, because ::assignedFun returns a CharSequence and a CharSequence is an Any, so this conforms with ::f3's type. In other words, if the arguments are compatible, any function returning a subtype of the original's return value is assignable.

Intuitively, any function that can compute a more specific result (i.e. a subtype instance) from the same amount information can be used — if we have an algorithm that produces a CharSequence, it can definitely produce (actually, it is producing) an Any.

This means that, since CharsSequence is a subtype of Any, (Number) -> CharSequence is a subtype of (Number) -> Any.

Is a function with a more general return value assignable?

No.

Intuitively, we can’t pretend we know how to create Strings when all we know is how to create CharSequences. This means that, (Number) -> CharSequence is not a subtype of (Number) -> String. It is in fact its super-type, as we saw above, which, again, is another reason why the answer is ‘no’ — one cannot be both a subtype and the super-type of another type.

We can see that, when we want to substitute one function for another, we can vary the types in opposite directions — types that go in can be any super-type, while types that come out can be any subtype. The words we used in this sentence — in, out — are no coincidence, and are the basis of technical jargon that will be discussed in the following chapter.

Also, notice that a problem arises when a type goes both in and out:

This is precisely why problems arose when we changed val to var in the Result example of the previous chapter. As long as the value property was val, there was no place in Result where T went in — it always went out (val only generates getters). However, once we changed it to var, we suddenly got both a getter (where T comes out) and a setter (where a T goes in). Same with Comparable — the type T goes in in the compareTo method, and out in the getter.

And that is the rule — when we write a generic construct where the generic variable goes in in some places, and out in others, it is impossible to consistently translate sub-/super-type relationships between different T’s to sub-/super-type relationships between generic constructs involving T’s, without allowing runtime errors to happen. If the T’s only go in, or only go out, it’s possible to do that, but it’s not required, and doesn’t happen automatically. The in situation is called contravariance, the out situation is called covariance, and we’ll talk about them more in the following chapter.

Understand that all of these facts follow directly from a single fundamental principle of OOP — that a subtype is only assignable to itself or its supertypes. All of the above is completely general, and not bound to any single language. It arises implicitly with any implementation of generics, in every language that observes this simple OOP principle.

Armed with this knowledge, let’s recap what variance is all about — it is about being able to determine which of the above scenarios is permissible for a certain generic construct. And the way you do that is by studying if the generic variable appears only in in positions, only in out positions, or a mix of both. That’s all there is to it.

The following chapter will talk about keywords in Kotlin (in and out) that you use when one of the above situations is permissible and you specifically want to allow it.

Go back to Generic Variance — Motivation, jump to the Table of Contents, or continue to Covariance, Contravariance, Invariance.

Join me in Etnetera
Kotlin
Java
Programming
Object Oriented
Generics
Recommended from ReadMedium