New operators


#8

I think that having three different equals operators in the language runs very much against Kotlin’s ambitions to be clear and simple.


#9

Well, I think there is a saying that you should make everything as simple as possible, but not simpler. The problems remain, and having everyone implement their own work-arounds certainly does not make things more simple nor clear. If you want to write some business logic, for instance, you need BigDecimal or some equivalent, and it is not pretty to have to write:

value1.compareTo(value2)==0
instead of
value1 = value2


Overloading == with different types of operands
#10

Looks like this problem can be solved much more easily by providing a different implementation of BigDecimal than by adding a new operator (not existing in any other language) to Kotlin.


#11

That would solve (BigValue1 == BigValue2) but not (BigValue == 5). The best I can do is define functions so I can write (BigValue == 5.B) or (BigValue eq 5), but the latter gets wrong precedence. The heart of the matter is that compareTo can handle different types, but equals can not (and should not). Actually, I think BigDecimal does the right thing in not equalling objects with different scales. It’s not just BigDecimal, it’s that something other than equals is needed for comparing different numeric types numerically. And compareTo works well in this regard, it’s just that the comparison test operators series is incomplete and so can’t be used for clear and consistent-looking code.


#13

Unless I’m missing something fundamemental, it seems to me that the simplest solution to all of these issues with ‘ordered’ classes would be for the compiler to link the implementation of the '==' and '!=' operators for a class C :-

  1. To the compareTo() method (i.e. a return value of zero or non-zero) if it implements Comparable<C>; or

  2. To the equals() method as it does at present for all other cases.

The equality operators would then meld in with the comparison operators for ordered classes which seems to be what we all want.


#14

@alanfo, yes, I think that is what Groovy does. That would be fine by me, though it breaks the simple connection between == and equals. But if we don’t want separate operators, it’s probably the best we can do. == will have to serve both purposes, and to distinguish manually we will have to use equals/compareTo. There is some discussions on this on the Groovy forums. But I don’t really suppose it’s feasible to change the operation of the == operator at this stage.


Inconsistent operator type conversions?
#15

I agree that the chances of this being changed now are remote.

If this is the way that Groovy implements equality, then the likelihood is that it’s been considered already and rejected for some reason.


#16

Another solution for Int vs Long comparison is implicit conversions, which, unlike new overloadable hieroglyphs :), is on the table (in the scope related to Kotlin Native, unsigned types, and making life better for those who twiddle bits using Kotlin).

@yole Note that an alternative implementation of BigDecimal will be faulty due to asymmetric equals.


#17

I didn’t know that implicit conversions were being considered though I’ve always felt that implicit ‘widening’ conversions for the integral types (Int to Long, Short to Int etc.) are fairly harmless.

Can you say whether (the more dangerous) implicit conversions between integral and floating-point types are also on the table and whether this would apply to all versions of Kotlin, not just Native?


#18

This will apply to all versions of Kotlin, not just Native.
Design is on a very early stage. Can’t provide any details yet. I personally would expect that “dangerous” conversions have to be explicit.


#19

But wait, it just hit me that assignment is not an expression in Kotlin, right? So there would be no conflict in using = also for comparison. When you write
(if x = 5)
instead of the compiler emitting “only expressions are allowed in this context”, it could translate = to compareTo()==0, or, to minimize risk of breaking existing code, to some new method comparesAsEqual() (if it exists on both objects, those who want to use it could implement it as extensions).

This way, we don’t need a new (ugly) operator, but could use the quite natural =, which would be the obvious choice had it not been appropriated for assignment. (We could still add <> for testing negative).


#20

Well, I certainly prefer that to your original proposal, though I’m more enthused now about what @Dmitry_Petrov_1 has said regarding the possibility that implicit widening conversions might be introduced which would solve the problems we have at present when testing equality between different integral types

I’m less concerned about equality comparisons between custom numeric types and integral types where I consider that it might not necessarily be a bad thing for these to remain explicit.


#21

I like what @nbengtg is proposing (=


#22

I think that having three different equals operators in the language runs very much against Kotlin’s ambitions to be clear and simple.

@yole But the real problem is not having three different operators, but having three different notions of equality. And this is already the case and can’t be changed. Whenever there’s something like >=, then a corresponding equality operator should be provided. As the consistency of compareTo and equals can’t be enforced, the induced equalities will differ.

Given three notions of equality, having three corresponding operators is the simplest solution.

@nbengtg To me, recycling = for equality is worse than using a new third operator, assuming we can find something readable. For "not equal according to compareTo“, <> is the obvious choice, with the mnemonics of "compareTo returns something < or > than zero” or simply “less or bigger”. For the opposite, I’d suggest !<> which reads as “neither less nor bigger”. This way, all operators based on compareTo contain some < or >, which makes it pretty clear.

@Dmitry_Petrov_1 You’re surely aware that int -> float and long -> double are lossy and you won’t repeat the Java mistake to call them widening, will you? The only really widening conversions between integral and floating-point types in Java are byte/short -> float and byte/short/int -> double.


#23

I like <> (and !<>) operators. They look funny, it means that reader will notice them. It’s a good idea, if a problem is real (honestly I can’t imagine the situation where I would want to use it, but my imagination might be limited).

What I don’t like is operator ===. It shouldn’t exist. Problem with Java’s == is that it means something different for referential types and it was easy to compare things like strings or boxed numbers and sometimes it would even work. Kotlin solved this problem, but === is easy to mistype when you wanted to type ==, it’s hard to spot, it will work sometimes and surely it’ll introduce subtle bugs. It should be replaced by something like x referentiallyEquals y.


#24

The fact that some classes implement compareTo() inconsistently with equals() is a corner case, and is important only in extremely rare cases. Having three different kinds of comparison operators in the language is a very visible feature, and will be greatly confusing to all learners of Kotlin, including those who will never in their life encounter a situation where the difference matters.


#25

@yole I don’t find it confusing at all, but people who didn’t read this discussion probably do, especially programming beginners. It’s true that the difference matters only very rarely. So I agree with you.

Another thing is the comparison of different numeric types. I guess, there’s no chance of making the above example

val a = 1
val b = 1L
a >= b gives true
a == b is not testable

work for == as it’s bound to be Java equals and it’s already defined for all operand combinations. So you can’t make 1 == BigInteger(1) work, but you can make 1 >= BigInteger(1) return true by providing an overload for compareTo. Now we have a much better use case for <> and !<> as they can use the overload. You can make it work for all combinations of predefined Kotlin data types and users can extend it for their own types.


#26

@maaartinus This is exactly my point. @yole I don’t think this is a corner case. As I said above in reply to

Looks like this problem can be solved much more easily by providing a different implementation of BigDecimal

equals() and compareTo serve different purposes and are implemented differently. Specifically, I can implement compareTo(myType) for any numerical type by an extension function, but I can not extend equals() as it is defined on Any. It’s not BigDecimal that is the problem, it also goes for any custom numerical type.

As for this being important only in extremely rare cases, that, I submit, depends on what areas of programming Kotlin is being used for, which, conversely, will be correlated to how well Kotlin is able to express what is needed in these areas. I am just now considering Kotlin for implementing a considerable amount of business logic. I had hoped that Kotlin would enable me to write more readable code, as compared to (!) Java, but if I can write > instead of compareTo()>0, but not = instead of compareTo()==0, it will not be clearer, just inconsistent, and we will probably go with Java anyway (or possibly Scala). Which is kind of sad, because I like Kotlin very much in many respects.


#27

@nbengtg I see, I was just repeating your arguments. That’s because of this thread being damn long and because of me being a Java guy, where compareTo is the way of implementing Comparable and nothing more.


#28

I’m new to Kotlin and am having this problem with BigDecimal as it doesn’t like the fact that mathematically 0 and 0.0 are the same thing.

I understand that what our brain expects is not the same as what an object would be. It would be like saying a piece of paper with 0 written on it is exactly the same thing as a piece of paper with 0.0 written on it. They are not.

My point is, if you need numeric equality in your test, you need to transform both sides into the same thing, like you would test a String using equalsIgnoreCase() for instance.

My suggestions as a beginner, if people agree with me:

Use unscaledValue() in both sides of the test

or

Create your own extension functions for this equality check somewhat like this:

fun BigDecimal.equalsIgnoreScale(other: BigDecimal): Boolean {
    return this.unscaledValue() == other.unscaledValue()
}
fun BigDecimal.equalsIgnoreScale(other: Double): Boolean {
    return this.unscaledValue() == BigDecimal(other).unscaledValue()
}
.
.
.

Another option would be extension functions overwrite member functions with same signature. But that could cause some other problems, so it is a good thing that this is not possible.