sirksel
sirksel

Reputation: 787

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

Upvotes: 2

Views: 2950

Answers (2)

Max Lebedev
Max Lebedev

Reputation: 47

You can do it even with the result of the same type.

inline val<T> T.sign where T: Number, T: Comparable<T> get() = run {
    if (this.toDouble() < 0) (-1 as T) else (1 as T)
}

More optimization in memory - convert source number to byte:

inline val<T> T.sign where T: Number, T: Comparable<T> get() = with(this.toByte()) {
    if (this < 0) -1 as T else if (this > 0) 1 as T else 0 as T
}

Upvotes: 0

zsmb13
zsmb13

Reputation: 89668

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.

Upvotes: 1

Related Questions