Dmitry Lukianov
12/28/2020, 6:53 PMShawn
12/28/2020, 7:45 PMComparable
interface itself. The tl;dr is that not all Number
implementations can realistically be compared to each other, and that assuming desired behavior for comparison across disparate types that do not trivially have a total ordering is, at best, just plain frustrating for developers.
For example, what should 1.compareTo(Float.NaN)
return? What if someone writes a complex number class? Do they just stub out compareTo()
and hope nobody ever calls it? Every Number
implementation would have to anticipate being compared to every other Number
implementation in order to be able to convert to a comparable type (and ideally be smart enough to avoid losses in precision), which is a convention that’s 1) incredibly brittle and 2) really annoying lol. You could just throw if you run into a type that isn’t handled or comparable to the value you’re comparing to, but that would defeat the point and also be more type unsafe than the existing hierarchy.
Much has been written on the topic, would recommend a read: https://stackoverflow.com/questions/480632/why-doesnt-java-lang-number-implement-comparableDmitry Lukianov
12/28/2020, 8:09 PMpublic final operator fun compareTo(other: kotlin.Byte): <http://kotlin.Int|kotlin.Int> { /* compiled code */ }
public final operator fun compareTo(other: kotlin.Double): <http://kotlin.Int|kotlin.Int> { /* compiled code */ }
public final operator fun compareTo(other: kotlin.Float): <http://kotlin.Int|kotlin.Int> { /* compiled code */ }
public open operator fun compareTo(other: <http://kotlin.Int|kotlin.Int>): <http://kotlin.Int|kotlin.Int> { /* compiled code */ }
public final operator fun compareTo(other: kotlin.Long): <http://kotlin.Int|kotlin.Int> { /* compiled code */ }
public final operator fun compareTo(other: kotlin.Short): <http://kotlin.Int|kotlin.Int> { /* compiled code */ }
In case of Complex number it can be compared with all others numbers as a complex numbers because complex number is more generalized and there is no contradiction.
If Number will be close to mathematical number hierarchy - all must be okay.Shawn
12/28/2020, 9:18 PMAnd Int can be directly compared with Float.Right, the Kotlin devs have picked a strategy for comparisons between the two and implemented it. They deliberately chose to do so and did all the work to make it happen, which is very different from forcing everyone that extends Number to implement
compareTo(Number)
. Plus, this approach allows method overloading to pick the correct method to call instead of relying on runtime type checking. This is far more efficient and enables the type system to prevent someone from calling compareTo
with a Number argument that Int doesn’t know how to handle by making it illegal to represent.
Additionally, making a major change to the Number API would have complex ramifications for Kotlin/JVM because there’s a lot of platform type mapping there. AtomicInteger
and the other number classes under java.util.concurrent.atomic
all deliberately do not implement Comparable because they box values intended to be mutable, and something could update their values after being sorted. These classes implicitly now extend kotlin.Number
because of Java compatibility, and making Number implement Comparable would mean adding compareTo
to these classes, which is fraught with problems.
Ultimately, allowing developers using the Number class to opt into implementing Comparable with disparate types is safer and more explicit, and helps avoid making assumptions that do not necessarily align with developers’ expectations.Dmitry Lukianov
12/29/2020, 11:00 AMComparable
to Number
.
I can propose other one.
It possible to add class like:
abstract class RealNumber <T : RealNumber<T>> : Number(), Comparable<T> {
abstract operator fun minus(other: T): T
abstract operator fun div(other: T): T
abstract operator fun plus(other: T): T
abstract operator fun inc(): T
//maybe some others operators
}
And inherit from it Float
and others (Int
, Byte
, etc., besides atomic) from a subset of real numbers.
Such way:
class Float : RealNumber<Float>() {
override fun minus(other: Float): Float {}
override fun div(other: Float): Float {}
override fun plus(other: Float): Float {}
//...
It can add possibility to provide common logic of calculation and comparison for all numbers with minimum changes (Float
and any others classes API does not change, types will be safe with minimum overhead).
What specific problem am I facing (Example of problem that can be resolved)
I have generic class like this:
open class Interval <T>(private val start: T, private val end: T)
: Comparable<ValueInterval<T>>
where T: Comparable<T>, T: Any {
fun isInside(value: T) : Boolean {
return value > start && value <= end
}
override fun compareTo(other: ValueInterval<T>): Int {
return this.start.compareTo(other.start)
}
//others compare operations
}
I want to extend it with some number specific functions (use plus/minus etc). Currently it can be added only for each separate Number
child (Double
, Int
).
With using RealNumber
it can be like this:
class RealNumberInterval<T : RealNumber<T>> (
private val start: T,
private val end: T
) : ValueInterval<T>(start, end) {
fun splitNotPrecise(numberOfParts: T) : List<RealNumberInterval<T>> {
val diff = (end - start) / numberOfParts
val realIntervals : MutableList<RealNumberInterval<T>> = ArrayList(numberOfParts.toInt() + 1);
var incr = start
do {
realIntervals += RealNumberInterval(incr, incr + diff)
incr += diff
} while (incr < end)
return listOf(RealNumberInterval(start, end))
}
//others compare with math operations
}
What do you think about it?