Search code examples
kotlinkotlin-extension

How do I extend Kotlin Number class or use generics to create a simple property getter which will operate on all Number subclasses?


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...


Solution

  • 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.