J-bob
J-bob

Reputation: 9126

Stopping generics from inferring Any type in Kotlin

I understand what's going on here, but I don't know the right way to fix it. Consider the following code:

data class MyClass<T>(val a: T, val b: T)

val myVal = MyClass(1,2)
val myVal2 = MyClass(1,"b")

myVal1 is inferred to a type of MyClass<Int>. This is what I want. But myVal2 is inferred to a MyClass<Any>.

I understand that it's because Any is a supertype of an Int and a String, but what I really want is a compiler error to get raised because 1 and "b" are not matching types.

I would like to accomplish this with type inference, rather than explicitly declaring MyClass<Int>.

I've looked at several other questions, but I they say that it's because the KProperty object uses an out T declaration. I don't have that issue here.

Motivation

I want this to build a class for defining comparison relationships, such as "equals", "greater than", etc. In my case I actually do want to use a KProperty. For example,

data class equalTo(val prop: KProperty<*,T>, val compareTo: T).

Basically, the compiler should stop you if you try to define:

val myCompare = equalTo(MyClass::myStringProperty, 123)

Since it is nonsensical to ask if a String property is equal to an Int. There might be a better way to implement this concept. If so, please let me know.

Upvotes: 0

Views: 176

Answers (2)

Tenfour04
Tenfour04

Reputation: 93882

There's no way to ensure they are the exact same subtype at the compiler level. This would also be an ambiguous concept. Two classes from different class hierarchies could be the exact same type if they implement the same interface.

But you should only care if the two of them have the same specific functions/properties you intend to call on them. In this case, you described wanting to compare their sizes. So what you actually need is for both values to be comparable by the same type, and you can bound the type as Comparable<T>.

data class MyClass<T: Comparable<T>>(val a: T, val b: T)

You could also use this with a property, sort of how you described, although a KProperty has only one generic type.

data class MyClass<T: Comparable<T>>(val prop: KProperty<T>, val b: T)

Upvotes: 2

gidds
gidds

Reputation: 18617

I don't think this is possible.

In your example, an Int is an instance of Any.  It's actually an instance of a subclass, but a subclass can do everything its base class can do, and is in all respects an instance of the base class; that's the Liskov substitution principle in action.

A String is also an Any in the same way.

So you have two Anys, and everything works out.  Even if you were to do something clever with variance or type bounds, I think it would still be able to infer a suitable supertype in this way.

It might help if you could explain what you're trying to achieve by this, and why the Any inference isn't acceptable to you.  Would you accept a call with, say, an ArrayList and a CopyOnWriteArrayList (which have no common ancestor other than Any/Object, though they do implement some common interfaces)?  Or ArrayList and LinkedList (which both extend AbstractList)?  What if listOf() were enhanced in future to return specialised classes for very small lists (like Scala does), so that listOf(1) returned a different class from listOf(1, 2) — would those be acceptable?  If not, why not?

Upvotes: 0

Related Questions