"maxBy" and nullable types


#1

Here’s a reduced case of a problem I’ve been facing:

class X(val x: Int)
arrayOf<X?>(X(1), X(2)).maxBy {e: X? -> e?.x ?: -1 }

This begets this error:

Kotlin: Type inference failed: Cannot infer type parameter T in inline fun <R : kotlin.Comparable<R>, T : kotlin.Any> kotlin.Array<out T>.maxBy(f: (T) -> R): T?
None of the following substitutions
receiver: kotlin.Array<out X?>  arguments: ((X?) -> kotlin.Comparable<R>)
receiver: kotlin.Array<out kotlin.Any>  arguments: ((kotlin.Any) -> kotlin.Comparable<R>)
can be applied to
receiver: kotlin.Array<X?>  arguments: ((X?) -> kotlin.Int)

Since Int extends Comparable<Int>, it seems that this error message is misleading. Does anyone know what’s going on?

The problem is related to nullability, since the following code works just fine:

class X(val x: Int)
arrayOf(X(1), X(2)).maxBy {e -> e.x }

Thanks!


#2

Seems to be a bug. I just tried to change the signature of maxBy from

fun <R : Comparable<R>, T : Any> Array<out T>.maxBy(selector: (T) -> R): T?

into

fun <R : Comparable<R>, T : Any?> Array<out T>.maxBy(selector: (T) -> R): T?

which works nicely in both cases (arrayOf<String?> and arrayOf).


#3

I haven’t been able to reproduce it either with beta4 or with latest master build.
Here is the first example working on try.kotlinlang.org:
http://try.kotlinlang.org/#/UserProjects/7nb20u44lm2f34kn85je2o6f37/kjm6q4ll7sbntkd71kracv94nf


#4

Thanks Palador.

If I may ask, how did you “change the signature”, by editing Kotlin’s source code directly?

For my part I tried my hand at a temporary fix like this:

fun <R : Comparable<R>, T : Any?> Array<out T>.maxBy(selector: (T) -> R): T?
{
    return this.filterNotNull().maxBy(selector)
}

But I’m given the error:

Kotlin: Type parameter bound for T in fun <T : kotlin.Any> kotlin.Array<out T?>.filterNotNull(): kotlin.List<T>
 is not satisfied: inferred type T is not a subtype of kotlin.Any

So if I get this straight, it says that T (in my own function, let’s call it T1), is a descendant of Any? and thus not a descendant of Any (fair enough). But this stems from the fact it tries to use T1 as T2 (T in filterNotNull()), while it should use T1 as T2? and infer from that. Not very sure about any of this.

Can anyone shed a light on this?


#5

I was using plugin version “1.0.0-beta-2428-IJ143-18” and now updated to plugin version “1.0.0-beta-4584-IJ143-12” (presumably this is beta 4?).

It does work now, so thanks a bunch :slightly_smiling:

I’d like to note that IDEA prompted me twice to update the Kotlin plugin, which I accepted both times, and each time the notification went away without much visible effect (given the version I was on, I supposed it failed).

If someone can still answer my question on my attempted fix above, it’d be wicked :]


#6

This is known limitation of constraint system in Kotlin type inference. I’ve reported an issue KT-10708 to be able to track that.

Currently the workaround would be to define your maxBy the following way:

fun <R : Comparable<R>, T: Any> Array<out T?>.maxBy(selector: (T) -> R): T?
        = this.filterNotNull().maxBy(selector)

Notice T : Any constraint in the function declaration.