1

Here is a concrete example of what I'm asking about:

MutableList[ImmutableList[Object]]

Where ImmutableList is covariant wrt its first parameter, but MutableList is invariant wrt its first parameter. Object is our "root type" that all types are a subtype of.

Is the type MutableList[ImmutableList[String]] a subtype of MutableList[ImmutableList[Object]] (assuming String is a subtype of Object) or is the only subtype of MutableList[ImmutableList[Object]] another MutableList[ImmutableList[Object]]? Is this language specific?

Any insights or articles are very much appreciated.

Glorfindel
3,1676 gold badges28 silver badges34 bronze badges
asked Aug 28, 2020 at 0:00

1 Answer 1

3

TL;DR No, MutableList[ImmutableList[String]] is not a subtype of MutableList[ImmutableList[Object]].

You can approach the problem intuitively by thinking about what's possible to do with the type. If MutableList has a "push" operation, for instance:

  1. You can push an ImmutableList[Object] into a MutableList[ImmutableList[Object]].
  2. ImmutableList[Integer] is a subtype of ImmutableList[Object].
  3. Therefore, you can push an ImmutableList[Integer] into a MutableList[ImmutableList[Object]].
  4. However, you cannot push an ImmutableList[Integer] into a MutableList[ImmutableList[String]];
  5. therefore, MutableList[ImmutableList[String]] is not a subtype of MutableList[ImmutableList[Object]].

This really is an explanation of why MutableList[T] is invariant in T: because it supports push.

It's also possible to work out the answer using the definitions of covariance and invariance.

ImmutableList[T] is covariant in T. This means that if A <: B, then ImmutableList[A] <: ImmutableList[B]. (I'm using the symbol <: for "is a subtype of")

  • Take A = String and B = Object. String <: Object, so ImmutableList[String] <: ImmutableList[Object].

MutableList[T] is invariant in T. This means that even if A <: B, MutableList[A] and MutableList[B] do not participate in a subtype relationship; they are incompatible types.

  • Take A = ImmutableList[String] and B = ImmutableList[Object]. Even though ImmutableList[String] <: ImmutableList[Object] (as we showed above), MutableList[ImmutableList[String]] and MutableList[ImmutableList[Object]] still do not participate in a subtype relationship.

Covariance and contravariance are two ways the subtype relationship can "leak out" of a type constructor. Invariance is what happens when the subtype relationship doesn't leak at all, so you can't cascade invariance with anything else and get anything other than invariance.

The meaning of different kinds of variance is not language specific, but there are some languages with mutable containers that are not considered invariant. Java is one example, and you can abuse this loophole in the type system to make programs that fail with runtime type errors despite compiling fine.

answered Aug 28, 2020 at 3:14
2
  • 1
    The point is that you can add an ImmutableList[Object] to a MutableList[ImmutableList[Object]] but not to a MutableList[ImmutableList[String]]. So the later doesn't comply with the contract of the former. Commented Aug 28, 2020 at 9:03
  • @FlorianF Thanks, I like your more intuitive explanation, so I overcomplicated it and added it to the answer :-) Commented Aug 28, 2020 at 14:19

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.