The original linkKotlin’s new version also has crossover and union types?

Kotlin 1.4 will use a new type derivation algorithm by default, and the type system is much more powerful than before.

After the release of Kotlin 1.4-M1, I took a look at some of the syntax updates mentioned in the official blog. See a preview of the new Kotlin 1.4 features. In addition to the changes mentioned in the previous article, the new type derivation algorithm for our ordinary code writing will be improved in many ways, next we will show you a case, to feel the new version of the powerful place.

1. Type derivation of branch expressions

Let’s start with some code:

Listing 1: The branch expression for Kotlin

    val number = if (validation()) 1F else 2.0
Copy the code

What is the type of number?

Our intuition tells us that number should be number, because the branches are Float and Double, and number is the parent of both, so number is ok.

Logically, that’s true, but in practice, there’s a whole lot going on. As you might expect, Float also implements a Comparable interface, while Double implements Comparable, so Float and Double should be subclasses of Comparable as well. Float and Double have two parent classes (interfaces), so what type is number? Comparable or Number?

Neither. In Kotlin 1.3, we can easily get the answer via IntelliJ:

Are you surprised? It’s Any. Because the Kotlin compiler doesn’t really know how to make a choice in this ambiguous case of type derivation, it simply doesn’t.

Of course, if you add a type declaration to number, for example:

Listing 2: Add type information for the branch expression

    val number: Number = if (validation()) 1F else 2.0
Copy the code

The Kotlin compiler is relieved that number is of type number.

For a detailed analysis of this problem, I wrote an article two years ago val b = a? A is a Double. What type is b? , interested readers can check it out.

2. Derivation results of new type derivation algorithm

So the question is, will the new type derivation algorithm automatically help us choose the type we want? Well, to be honest, in this case the compiler doesn’t know what type you want, so it’s impossible to make a choice. Why choose when you can’t? Kids make choices. Of course I want them all!

What’s going on here? That’s a type I’ve never seen before. Indeed, such types are not explicitly declared, but can only be derived by the compiler. So what exactly is this {A & B} type? The literal meaning is both type A and type B, and the actual meaning is the same. That is, {Comparable{Double & Float} & Number} is both a Comparable and a Number type.

Thus, in Kotlin 1.4, the following code becomes legal:

Listing 3: Use of the branch expression type in Kotlin 1.4

    operator fun Number.compareTo(other: Number): Int {
        return this.toDouble().compareTo(other.toDouble())
    }

    val number = if (validation()) 1F else 2.0
    if (number > 2) {
        println("$number > 2")}else {
        println("$number< = 2")}Copy the code

This code will not compile by default in Kotlin 1.3.

3. Cross types and union types

A cross type of two types is the union of two types, so for types A & B, if we think of A and B as sets, it equals A ∪ B. The implication of Figure 3 actually relates to another concept: union types. For the article at the beginning of the branch expression, it is of type Double or Float, or Double | Float, this type is a joint type, in terms of the collection is actually a Double studying Float. Colloquially, A cross type is A relationship that is both A and B, while A union type is A relationship that is either A or B.

In that case, from the figure, the Comparable & Number = = Double | Float, because we have said before, a Double and Float a common parent class (interface) including Comparable and Number. Note that when Kotlin expresses the union type, he is actually taking an approximation of a type, which is the common parent class.

To be honest, Kotlin’s union types are not the same as the true ideal union types. Let’s give you a taste of TypeScript union types:

Listing 4: Union types in TypeScript

    interface Bird {
        fly(): void;
        layEggs(): void;
    }
    
    interface Fish {
        swim(): void;
        layEggs(): void;
    }
    
    declare function getSmallPet(): Fish | Bird;
    
    let pet = getSmallPet();
    // OK, public member of both types
    pet.layEggs();
Copy the code

Although the Bird and Fish two interfaces have no common parent, but Bird | Fish are both members of the public layEggs. If this code were placed in Kotlin, the result would be predictable:

Listing 5: Kotlin’s union type

    interface Bird {
        fun fly(a)
        fun layEggs(a)
    }
    
    interface Fish {
        fun swim(a)
        fun layEggs(a)
    }
    
    val pet = if(validation()) object : Bird{ ... } else object : Fish{ ... }
    pet.layEggs() // Error
Copy the code

Pet theory should be a Bird here | Fish, but Kotlin compiler will always try to type “degradation” it into a system to express the type of the current type, the degradation method is to find the common parent classes, namely Any). Therefore, the Kotlin compiler deduces the type of the branch expression to Any, and pet naturally cannot access layEggs directly, even though Bird and Fish both have this function.

According to the Kotlin language specification, neither cross types nor union types in Kotlin can be declared directly, but only in syntactic phenomena such as intelligent type conversions. Once such types are created, Kotlin uses methods such as type approximation, type degradation, and so on to find a suitable type in the existing type system to express them.

4. Why not just support cross and union types?

In fact, this issue has been debated for a long time. According to the discussion of all parties, there are the following reasons why Kotlin has not officially introduced such a type at present:

  1. False demand. Some of the use cases provided by the developers supporting the introduction of this feature can be implemented mostly through features such as function overloading, generic constraints, and in some cases it is actually better to optimize the type design rather than hope for a more complex type system.
  2. There is a risk of abuse. Just a little bit of type system complexity can lead to a huge increase in project code complexity. This is evident in Kotlin’s support for function types, but the difference is that function types are really needed.

What do you think about that? Comment in the comments below.


If you want to get started with Kotlin quickly or want to learn more about Kotlin in depth, you can pay attention to my new course based on Kotlin 1.3.50. The first version of the course helped over 3000 students master Kotlin. This update is more exciting.

Scan the QR code or click the link “Kotlin Getting Started to Master” to enter the course!


For Android engineers who don’t have a chance to find a good job Offer and want to advance, check out my new course, Hack the Android Advanced Interview. Over 700 students are taking this course. (*≧∪ motion:)

Scan the QR code or click the linkHack Android Advanced InterviewReady to enter the course!