The Programmers Guide To Kotlin - Covariance & Contravariance
Written by Mike James   
Monday, 21 January 2019
Article Index
The Programmers Guide To Kotlin - Covariance & Contravariance
Generic Variance
Type Projections

kotlinlogo

Type Projections

Type projections solve the problem of changing the behavior of a generic class after it has been declared. It also allows you to take a generic entity and while it might not use the type parameter as a pure input or output you can promise to use it as strictly input or output.

For example, consider the array. This is by default invariant and you cannot sensibly redefine its declaration to make it contravariant or covariant because it makes little sense to have array elements that are exclusively read- or write-only. Instead you can use in and out in type modifiers to mark types that will only act as consumers or producers and hence define the type’s variance.

For example:

var a:Array<Any>
var b = Array<Int>(10,{0})
a=b

produces a compile-time error because Array is invariant.

You can't change the fact that arrays are invariant, but you can define the type of a so that it is a consumer:

var a:Array<out Any>
var b = Array<Int>(10,{0})
a=b

The out modifier says that a will only be used to access array elements and this is safe because the underlying elements are ints which, as a derived type of Any, have all the methods and properties required.

What is important here is to realize that this projection is mostly a trick to allow the upcast to pass the compiler's type checking. The elements of the array referenced by a are still ints. What stops you from assigning to a[i] even though you have promised you won’t? The simple answer is that a[i] is regarded as an Int so assigning, say, a string to it will fail because the types are incompatible.

You can try the same trick with a covariant in:

var a:Array<in Int>
var b = Array<Any>(10,{0})
a=b

without the in this too generates a compile-time error. With the in it compiles and you can store values of type Any in elements of a. The compiler allows the assignment but in this case there is nothing to stop you from using a as a source or a consumer of values. That is:

a[1]="abcd"
println(a[1])

works and if you expect a[1] to be an Int just because a is Array<in Int> you are going to be disappointed. This type projection doesn't work as well as it could.

If you use a projection as a function parameter, then the compiler will also check that you are playing by the rules. For example:

fun myFunc(myparam:MyClass<out Any>){
    println(myparam.read())
    myparam.write(10)
}

In this case the compiler will complain about the use of write which attempts to change the value.

Similarly if you define the function as:

fun myFunc(myparam:MyClass<in Int>){
    println(myparam.read()+1)
    myparam.write(10)
}

then the compiler will complain about the + operator as we cannot guarantee that the value is numeric.

The in and out modifiers used as type projections simply allow the up and downcasting of the type and tell you that it is safe to set or get elements.

The * Projection

Final version in book

Summary 

  • Generics are an attempt at allowing algorithms that work with a range of types to be written in a type safe way.

  • Kotlin generics, like those in Java and most other languages, make use of type parameters indicated by <T>. These are used in generic code as if they were a type specifier and assigned a value when the generic code is used to operate on a particular type.

  • The big problem in using generics is that you cannot assume anything about the type T even though you may know at run time what it is. This means you cannot call any methods or use properties beyond that possessed by Any.

  • Generic properties pose a particular problem because you cannot even initialize them as you don’t know their type at compile time.

  • You can create generic properties but only if you include them in the primary constructor so that they are guaranteed to be initialized at run time.

  • One approach to creating generic methods or functions that can do more than just work with Any is to pass a generic action function which has a defined type at compile time.

  • A second approach is to use type constraints. Kotlin provides the upper bound constraint which specifies the base class that the type must be derived from. This allows you to use the methods and properties of the base class within the generic code.

  • Variance is all about how data structures relate to one another when they are composed of related types. That is, if you construct a new type involving an existing type then it is contravariant if the construction reverses the “use in place of” relationship. If you construct a new type involving an existing type then it is covariant if the construction follows the same the “use in place of” relationship. If there is no relationship then the construct is invariant.

  • Inputs tend to be contravariant, outputs covariant and general read/write types are invariant.

  • Generics and arrays in Kotlin are invariant by default.

  • You can modify this default variance by using the in and out modifiers when you declare the type – declaration-site variance.

  • You can also modify the variance when you use a type using the same in and out modifiers – use-site variance or projections.

  • The * projection lets you pass any instance of a generic, no matter what its type.

 

This article is an extract from: 

Programmer's Guide To Kotlin Second Edition

kotlin2e360

You can buy it from: Amazon

Contents

  1. What makes Kotlin Special
  2. The Basics:Variables,Primitive Types and Functions 
  3. Control
         Extract: If and When 
  4. Strings and Arrays
  5. The Class & The Object
  6. Inheritance
  7. The Type Hierarchy
  8. Generics
  9. Collections, Iterators, Sequences & Ranges
        Extract: Iterators & Sequences 
  10. Advanced functions 
  11. Anonymous, Lamdas & Inline Functions
  12. Data classes, enums and destructuring
        Extract: Destructuring 
  13. Exceptions, Annotations & Reflection
  14. Coroutines
        Extract: Coroutines 
  15. Working with Java
        Extract: Using Swing ***NEW!

<ASIN:B096MZY7JM>

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner


JetBrains Updates IDEs With AI Code Completion
04/04/2024

JetBrains has launched the first set of updates for 2024 of its JetBrains IDEs. The new versions include full-line code autocompletion powered by locally run AI models.



AWS Lambda Upgraded To .NET8 Runtime
25/03/2024

An upgrade of AWS Lambda to the .NET version 8 runtime
brings major improvements to the platform.


More News

raspberry pi books

 

Comments




or email your comment to: comments@i-programmer.info

<ASIN:1871962536>

<ASIN:1871962544>



Last Updated ( Monday, 21 January 2019 )