3

I am building a Matrix class and want to be able to store Numbers in a 2d Array.

var data: Array<Array<Number>> = Array(width, {Array(height, {0})})

This does not work because Array<Number> and Array<Int> are invariant. I can make it work by using Array<Array<out Number>>, but the Matrix will be immutable, and I don't want that...

Casting {0 as Int} makes the compiler error go away, but this does not seem like a good idea. I also want to do things like addition and I noticed that it's not possible to add Numbers:

var n: Number = 1
n + 1 // does not work

So how could I solve this problem? And why exactly can I not add two Numbers?

Patric
  • 1,489
  • 13
  • 28

1 Answers1

2

Number is an abstract class and does not define anything for addition. And since there's no defined method for adding the numbers, you can't do numberInstane + otherNumberInstance. You can, however, create an operator function for it:

infix operator fun Number.plus(other: Number) : Number{
    return when (this) {
        is Double -> this + other.toDouble()
        is Int -> this + other.toInt()
        is Long -> this + other.toLong()
        is Float -> this + other.toFloat()
        is Short -> this + other.toShort()
        is Byte ->  this + other.toByte()
        else -> 0
    }
}

Note that this only applies to addition. The rest of the functions would follow the same pattern, but replacing the operator (here it's +) and the name of the function (here it's plus).


As mer msrd0's comment, the above would result in 1 + 1.5 being 2, because it rounds down. Kotlin supports adding number types across each other, which ends up with this slightly horrid solution:

infix operator fun Number.plus(other: Number) : Number{

    when {
        this is Double -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Int -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Long -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Float -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Short -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Byte -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        else -> return 0
    }
}

The nested when-statement helps autocasting the values, which is necessary since Number isn't a specific known class. There might be a better solution though, but without knowing the specific type. The extension function is mostly just auto-casting based on the type, but there can't be a single variable because it would need to be defined as a Number to accept all the types, and since there are two vars where both need proper casting based on the passed type, it ends up being slightly messy.

Zoe
  • 27,060
  • 21
  • 118
  • 148
  • Ok, I see.. so I guess doing this would work, but it does not seem like a clean solution :/ – Patric May 13 '18 at 13:53
  • @Tagas it's a slightly hackish solution, but it's the easiest option without manually casting. – Zoe May 13 '18 at 13:54
  • 2
    While your solution might work, one might argue that `1 + 1.5` should be `2.5`, while your plus operator function would return `2` since you only check the type of `this`, not `other` – msrd0 May 13 '18 at 16:13
  • @msrd0 you're right. I've edited my answer (all though it's a kinda messy solution) – Zoe May 13 '18 at 17:04