SlaneR
SlaneR

Reputation: 714

Kotlin - Why do we have to explicit type parameter(s) for generic method?

I'm working on extension method like this:

infix fun <T> T.isNullOr(other: T): Boolean {
    if (this == null) return true
    return this == other
}


and I'm trying to use this method like this.

val thisShouldWork = true isNullOr true // this is true
val thisShouldNotWork = true isNullOr 0 // No compilation errors?


I expected compilation error because type parameter is automatically set to Boolean for isNullOr but it wasn't. What's happening?

am I misunderstanding about it?


in C#, same code working well as I expected.

static bool IsNullOr<T>(this T t, T other) {
    if (t == null) return true;
    return Equals(t, other);
}

bool howAboutThis = 0.IsNullOr(0);
bool andThis = 0.IsNullOr(false); // error - cannot detect type parameter for this

Upvotes: 2

Views: 1627

Answers (5)

Roland
Roland

Reputation: 23352

Just remember that generic type information is erased at runtime and whenever you try to put something into a method that accepts generics, then the common denominator is assumed, e.g.:

listOf("one", 123) // -> assumes T:Any and therefore gives List<Any>

Now for your example that would mean "one".isNullOr(123) both become Any.

As a sidenote however, if you declare a specific type (e.g. List<String>) as shown next, it will not work to assign a different type to it:

val test : List<String> = listOf(123) // this will not work

It is already known at compile time that the given int can't become a string. This sample however doesn't help you as you do not return that generic type. If your method just looked a bit different, e.g. would have a generic type as return value, it might easily have worked out similar to the List-sample before.

So to fix your sample you need to specify the type which will basically make the infix obsolete, e.g. the following will work as you expect:

val someString : String? = TODO()
val works = someString.isNullOr<String?>("other")
val doesntWork = someString.isNullOr<Int?>(123) // does not nor does:
val doesntWorkToo = someString.isNullOr<String?>(123)

Note that for what you've shown some standard functionality might help you (but not eliminate that specific problem), i.e. using the ?: (elvis operator) with a ?.let:

val someVal : String? = "someString given from somewhere"
val thisWorks = someVal?.let { 
                     it == "some other string to compare" 
                } ?: true /* which basically means it was null */
val thisWillNot = someVal?.let {
                     it == 123 // compile error (funny enough: it.equals(123) would work ;-)
                  } ?: true /* it is null */

Upvotes: 1

Alexey Romanov
Alexey Romanov

Reputation: 170899

If you really want to prevent it, you can:

class IsNullOr<T>(val x: T) {
    operator fun invoke(other: T): Boolean {        
        if (x == null) return true
        return x == other
    }
}

fun <T> T.isNullOr() = IsNullOr(this)

fun main(args: Array<String>) {
    val thisShouldWork = true.isNullOr()(true) // compiles
    val thisShouldNotWork = true.isNullOr()(0) // doesn't compile
}

This makes type inference depend only on the receiver of isNullOr. If vals could be generic, you'd even keep the original syntax (but they can't).

Upvotes: 0

SlaneR
SlaneR

Reputation: 714

Thank for answers. I think there is no way to prevent this at compilation level, so I decided to check type for other.

inline infix fun <reified T> T.isNullOr(other: T): Boolean {
    if (this == null) return true
    if (other !is T) return false
    return this == other
}

Upvotes: 0

s1m0nw1
s1m0nw1

Reputation: 82087

I think in this case the generics don't really matter. You only call equals in the method, which you can do on any type. It's basically the same as:

infix fun Any.isNullOr(other: Any): Boolean {
    return this == other
}

It compiles without problems because you can always call equals with anything: other: Any?

Upvotes: 1

nyarian
nyarian

Reputation: 4375

Here, val thisShouldNotWork = true isNullOr 0 is equal to val thisShouldNotWork: Boolean = true.isNullOr<Any>(0). Type parameter as inferred as the closest parent.

And function's return type is based on logical expression evaluation: this == other. Let's see == function declaration: public open operator fun equals(other: Any?): Boolean. It receives Any?.

Type parameter in this function has nothing to do with Boolean.

Upvotes: 3

Related Questions