Search code examples
kotlintypesinterfacemultiple-inheritance

Understanding Kotlin Type system, What is meant by `{Comparable<{ Double & Int }> & Number}` and how to work with that


So for example:

var a = true
val test = if (a) -1 else -3.2

I was expecting the Type of the test should be the most closest intersection of the type-hierarchy i.e. for Int and Double is Number.

But looking at the IDE, it seems to have a type of {Comparable<{ Double & Int }> & Number}.

IDE Snapshot

And the weird thing is, I cannot specify it like that (since {} is reserved for creating lambdas), I can only set it to a type of Number.

And another wierd thing is that if I try some function from Comparable interface, it throws some error:

// Warning at value 2
// The integer literal does not conform to the expected type {Double & Int}
test.compareTo(2)

3.compareTo(-1.1) // possible
2.3.compareTo(100) // possible

// But why not this is possible, while it has inferred type of Comparable?
test.compareTo(2)

Could somebody help in understanding the concept here? And few questions:

  • How does that type work all together, i.e. how could one variable hold two types at once?
  • How could one specify that type explicitly?
  • How do you use functions from Comparable interface, when test has implementaion of it?

Solution

  • & here means an intersection type (which isn't supported in the Kotlin language itself, but the compiler uses them internally). You can see it mentioned in the (incomplete) specification.

    Intersection types are special non-denotable types used to express the fact that a value belongs to all of several types at the same time.

    "Non-denotable" means exactly that you can't specify that type. I am not sure but I think the extra { } in types are supposed to indicate exactly this.

    In particular, Comparable<Double & Int> means you can only compare test to something which is both Double and Int, but there are no such values. The compiler could probably simplify it to Comparable<Nothing>.

    the most closest intersection of the type-hierarchy i.e. for Int and Double is Number.

    1. It's least upper bound, which is closer to union, not intersection. The specification actually calls it "union types", but that's not the normal usage of that term.

    2. This least upper bound is not Number because it also takes least upper bound of the Comparable interfaces which works out to Comparable<Double & Int> because Comparable is contravariant:

       lub(Int, Double) = 
       Number & lub(Comparable<Int>, Comparable<Double>) =
       Number & Comparable<Int & Double>
      

    This calculation is described under type decaying:

    All union types are subject to type decaying, when they are converted to a specific intersection type, representable within Kotlin type system.