I am trying to learn more about Kotlin abstract class extensions and generics by building very simple methods and property getters that extend built-in classes. I've mostly been successful, but I'm stumped by the Number class. My test property Number.sgn
is intended to return the sign (1 or -1 as an Int) of any subclass of Number. For simplicity, negatives should return -1, while positive numbers and 0 should return 1. I'm not particularly interested in this method's use case, but for the understanding of how to write something simple like this -- and why my code generates the error it does. The only import in my module is kotlin.text.*
and the error message I receive does refer to a conflict there. I'm just not understanding why it conflicts and how to overcome it -- although I'm guessing it's a novice error.
I first wrote the code to extend the Int class, which works fine:
inline val Int.sgn get() = if (this<0) -1 else 1 // sign of number
I then tried to generalize and move it to the Number class, like this:
inline val Number.sgn get() = if (this<0) -1 else 1 // doesn't compile
and the compile error is as follows:
unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public fun String.compareTo(other: String, ignoreCase: Boolean = ...): Int defined in kotlin.text
inline fun Number.sgn() = if (this<0) -1 else 1
^
I then tried a different approach, using generics:
inline val <T:Number> T.sgn get() = if (this<0) -1 else 1
and I received the same error from the compiler:
error: unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public fun String.compareTo(other: String, ignoreCase: Boolean = ...): Int defined in kotlin.text
inline val <T:Number> T.sgn get() = if (this<0) -1 else 1
^
Could anyone help me understand why there is a type mismatch, and why kotlin.text
matters here? Is there an approach that I could use to overcome this problem and get this property getter to apply to all subclasses of Number? (Again, I know this isn't a meaningful use case, but rather a simplified example to help me understand the principles behind this.) Thanks in advance for any advice anyone can give...
Your first function works because Int
implements Comparable<Int>
, that's what the <
operator is translated to. However, if you look at the Number
class, you'll see that it only has functions in it for conversions to its various subclasses - it doesn't implement Comparable
, therefore, you can't use the <
operator on it.
What you could do instead is convert your Number
to a Double
first, and then see if it's negative:
inline val <T : Number> T.sgn
get() = if (this.toDouble() < 0) -1 else 1
You could also make your original code (either with or without generics) work by implementing the compareTo
function for Number
as an extension:
operator fun Number.compareTo(other: Number) = this.toDouble().compareTo(other.toDouble())
Just be aware that casting everything to Double
might result in losing precision.