Reputation: 7180
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
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
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
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