Reputation: 1408
According to the documentation for When Expression, it can replace "if-else if", so I tried implementing a function to return maximum of two variable of Any
type:
fun maxOf(a: Any, b: Any) = when {
a is Int && b is Int -> if (a < b) b else a
a is Double && b is Double -> if (a < b) b else a
a is Int && b is Double -> if (a < b) b else a
a is Double && b is Int -> if (a < b) b else a
a is String && b is String -> if (a < b) b else a
else -> null
}
The above implementation works but I thought it could be more concise:
fun maxOf(a: Any, b: Any) = when {
(a is Int || a is Double) && (b is Int || b is Double) -> if (a < b) b else a
a is String && b is String -> if (a < b) b else a
else -> null
}
But I failed because the second implementation doesn't work; the error is in the first occurrence of if (a < b)
:
Unresolved references.
None of the following candidates is applicable because of receiver type mismatch
• public fun String.compareTo(other: String, ignoreCase: Boolean =...): Int defined in kotlin.text
Is this because smart-cast is not capable of casting a
and b
to their actual types after evaluating the expression (a is Int || a is Double) && (b is Int || b is Double)
? Or am I missing something?
The same error happens even if the type of a
and b
is changed to Number
:
fun maxOf(a: Number, b: Number) = when {
(a is Int || a is Double) && (b is Int || b is Double) -> if (a < b) b else a
else -> null
}
Upvotes: 3
Views: 662
Reputation: 6569
It's not a problem of when
expression. You should blame on the type system.
a
and b
are instance of Int
and Double
, so Kotlin will infer them as their LCA. For example:
open class A { fun a() = println("meow meow meow") }
class B : A()
class C : A()
if (a is B || a is C) a.a() // smart cast works
However, Int
and Double
are subclasses of Number
and Comparable
at the same time, and Kotlin doesn't know if you want a Number
or a Comparable
, so Kotlin treat it as an instance of Any
.
if (a is Int || a is Double)
if (a > 1) print("meow meow") // smart cast doesn't work
This is why the "bug" appears.
Use your original code, or use explicit cast (I know it sucks, but it is really an unavoidable problem).
I've came up with a beautiful solution! Look at it:
fun <T : Comparable<T>> maxOf(a: T, b: T): T? = when {
(a is Int || a is Double) && (b is Int || b is Double) -> if (a < b) b else a
a is String && b is String -> if (a < b) b else a
else -> null
}
And in this way, you can apply any Comparable
to it (instead of Int
, Double
and String
only)!
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a < b) b else a
Your original version will return null
if input is invalid, and this version will raise compilation error if input is invalid, helping you find error at compile time.
You've commented me and asked a further question. I want to make my reply more readable so I'll add it to my answer.
fun someFunction(a: Number) {
if (a is Int || a is Double) println(a < 1) // still error! Why?!
}
Yeah, indeed, a
is an instance of Number
here.
But please do mention, <
, or, compareTo
, is not declared in Number
. It's in Int
or Double
:).
You said:
using generics forces
a
andb
to be the same type.
So try this:
fun <A : Comparable<B>, B : Any> maxOf(a: A, b: B): Any = if (a < b) b else a
And you said:
explicit cast is not a solution if the type of
a
andb
isAny
operator fun Any.compareTo
Upvotes: 3