I'm pondering a question about the minus operator ...
# stdlib
h
I'm pondering a question about the minus operator for Iterables. For reference, here's the signature:
public operator fun <T> Iterable<T>.minus(elements: Iterable<T>): List<T>
Is there a reason it's not defined as follows:
public operator fun <T> Iterable<T>.minus(elements: Iterable<*>): List<T>
(note the type of the argument
elements
) Consider the following code:
Copy code
val a = listOf(1, 2) // inferred type: List<Int>
val b = listOf(1, "string") // inferred type: List<Any>
val c = a - b // inferred type: List<Any>
With the alternate definition the type of
c
would always be equal to the type of
a
, regardless of the type of
b
. This would seem correct and useful to me.
👀 1
j
My first thought is this shouldn't even compile, this is very error prone.
h
Yet it does compile.. Even this compiles:
Copy code
val a = listOf(1, 2) // inferred type: List<Int>
val b = listOf("string") // inferred type: List<String>
val c = a - b // inferred type: List<Any>
I would understand (and appreciate) an error or warning when the element type of
a
is not a subtype of the element type of
b
(in those cases, the only possible result would be an empty list, unless maybe when you're working with a odd kind of
equals
...) For the case where the element type of
a
and
b
are not equal, but elements of
a
are a subtype of elements of
b
I think it has its uses. But I would accept it if there was an compile time error. I came across it in a situation a bit like this (playground)
k
It seems to be due to the way
contains
is defined:
Copy code
operator fun <T> Iterable<T>.contains(element: T): Boolean
By contrast, in Java the argument could be anything, as
contains
takes
Object
instead of `E`:
Copy code
public interface Collection<E> extends Iterable<E> { ... boolean contains(Object o) ... }
So it seems that in Kotlin the intention was to require the arguments to be of the same type as the receiver, but the ability of the type parameter to be
Any
is possibly unintended.
h
It saw that
contains
uses the
@kotlin.internal.OnlyInputTypes
annotation here:
public operator fun <@kotlin.internal.OnlyInputTypes T> Iterable<T>.contains(element: T): Boolean
. So, maybe that annotation should have been added to
minus
as well?
I'm don't really understand why this restriction on
contains
would need to affect
minus
though..
i
minus
was introduced with the same parameters as
plus
, but after some time, we thought about relaxing its signature in the way proposed in this thread. However, now it may be not easy, considering various compatibility concerns and taking caution not to allow meaningless operations like
List<String> - Int
h
Thanks for that background @ilya.gorbunov, makes sense how this came to be and I'm glad to hear that changes similar to my suggestion have been considered. I have found this issue by now and given it my thumbs up 🙂 I still don't really understand the considerations though. Until now the arguments I've seen against making such a change seem to be either: • "We don't want code to compile when the types (severely) mismatch." But this is already allowed, e.g.: an operation like
listOf("string") - 1
compiles and results in an inferred type of
List<{Comparable<*> & java.io.Serializable}>
• "It needs to be backward compatible." But it seems to me that the only effect would be a more narrow/specific return type, which shouldn't break anything I would think...