elect
elect

Reputation: 7180

Kotlin, generic operation on Number

I have the following

abstract interface Vec2t<T : Number> {    
    var x: T
    var y: T
}

data class Vec2(override var x: Float, override var y: Float) : Vec2t<Float>

and I have an interface where I define several operations, e.g:

interface fun_vector2_common {

    fun abs(res: Vec2, a: Vec2): Vec2 {
        res.x = glm.abs(a.x)
        res.y = glm.abs(a.y)
        return res
    }
}

Is it possible to implement, let's say abs, by using generics?

interface fun_vector2_common<T : Number> {

    fun abs(res: Vec2t<T>, a: Vec2t<T>): Vec2t<T> {
        res.x = glm.abs(a.x)  // error
        res.y = glm.abs(a.y)  // error
        return res
    }
}

And then the corresponding glm.abs() based on the type will be called?

The above code fails because it expects, obviously, a glm.abs(n: Number)

Upvotes: 5

Views: 4249

Answers (3)

eydryan
eydryan

Reputation: 121

Played with this a bit and I found a much faster solution to calculate mode, didn't try for the other two:

fun <T : Number> List<T>.mode(): T {
    return this.groupBy { it }.maxBy { it.value.size }.key
}

Upvotes: 0

Hakanai
Hakanai

Reputation: 12728

I had a similar design requirement for a similar use case, and the structure I ended up with was something like this:

class Vec2<T : Number>(
    val x: T,
    val y: T,
    private val valueAdapter: ValueAdapter<T>
) {
    fun length(): T = valueAdapter.sqrt(
        valueAdapter.add(
            valueAdapter.multiply(x, x),
            valueAdapter.multiply(y, y)
        )
    )

    companion object {
        fun ofDouble(x: Float, y: Float): Vec2t<Float> =
            Vec2(x, y, ValueAdapter.forFloat)

        fun ofDouble(x: Double, y: Double): Vec2t<Double> =
            Vec2(x, y, ValueAdapter.forDouble)
    }
}

// There's probably a better name for this interface. `Field`?
interface ValueAdapter<T: Number> {
    fun add(x: T, y: T): T
    fun multiply(x: T, y: T): T
    fun sqrt(x: T): T

    companion object {
        val forFloat: ValueAdapter<Float> = object : ValueAdapter<Float> {
            override fun add(x: Float, y: Float) = x + y
            override fun multiply(x: Float, y: Float) = x * y
            override fun sqrt(x: Float) = sqrt(x)
        }

        val forDouble: ValueAdapter<Double> = object : ValueAdapter<Double> {
            override fun add(x: Double, y: Double) = x + y
            override fun multiply(x: Double, y: Double) = x * y
            override fun sqrt(x: Double) = sqrt(x)
        }
    }
}

With this sort of layout, someone who has their own type they want to slot in can just implement their own value adapter for their custom type.

And in theory, you don't really need to restrict it to Number.

Also FWIW, I eventually ended up abandoning this structure, not because it doesn't work for the use case, but because I came across more use cases, for example, sometimes the square root of a Double returned a Complex. So I eventually wound up having a single set of operations allowing Any.

Upvotes: 1

miensol
miensol

Reputation: 41678

Unfortunately there's no clean way to have generic abs function. You can work it around with the following abs definition:

object glm {
    fun <T : Number> abs(x: T): T {
        val absoluteValue: Number = when (x) {
            is Double -> Math.abs(x)
            is Int -> Math.abs(x)
            is Float -> Math.abs(x)
            is BigDecimal -> x.abs()
            is BigInteger -> x.abs()
            else -> throw IllegalArgumentException("unsupported type ${x.javaClass}")
        }
        @Suppress("UNCHECKED_CAST")
        return absoluteValue as T
    }
}

Which would make it possible to use in your context:

fun abs(res: Vec2, a: Vec2): Vec2 {
    res.x = glm.abs(a.x)
    ...
}

Upvotes: 6

Related Questions