Eastsun Eastsun - 1 month ago 10
Scala Question

How to reduce the unwanted type parameter in a generic method?

I want to implement some generic math functions with some flexible.
e.g. a function named

meandot
which declared as something like

object Calc {
def meandot[..](xs: Array[Left], ys: Array[Right])(implicit ..): Result
}


where
meandot(xs, ys) = sum(x*y for x, y in zip(xs, ys)) / length


When I invoke the
meandot
without specialized type parameter, it should return a value with default type. e.g.

scala> Calc.meandot(Array(1, 2), Array(1, 1))
res0: Int = 1


If I invoke the
meandot
with specialized type parameter, it can return a proper value.

scala> Calc.meandot[Int, Int, Double](Array(1, 2), Array(1, 1))
res1: Double = 1.5


However, the first two type parameters in above are redundant. The only type I need to specialized is the return type. I want to invoke it simplified as

scala> Calc.meandot2(Array(1, 2), Array(1, 1))
res2: Int = 1

scala> Calc.meandot2[Double](Array(1, 2), Array(1, 1))
res3: Double = 1.5


And I found a way to implement it as following code, which using a proxy class
MeanDotImp
. But it seems not so elegant. So I wonder if there is any better solution to reduce the unwanted type parameter in a generic method?

trait Times[L, R, N] {
def times(x: L, y: R): N
}

trait Num[N] {
def zero: N = fromInt(0)
def one: N = fromInt(1)
def fromInt(i: Int): N
def plus(x: N, y: N): N
def div(x: N, y: N): N
}

abstract class LowTimesImplicits {
implicit val IID: Times[Int, Int, Double] = new Times[Int, Int, Double] {
def times(x: Int, y: Int): Double = x * y
}
}

object Times extends LowTimesImplicits {
implicit val III: Times[Int, Int, Int] = new Times[Int, Int, Int] {
def times(x: Int, y: Int): Int = x * y
}
}

object Num {
implicit val INT: Num[Int] = new Num[Int] {
def fromInt(i: Int): Int = i
def plus(x: Int, y: Int): Int = x + y
def div(x: Int, y: Int): Int = x / y
}

implicit val DOU: Num[Double] = new Num[Double] {
def fromInt(i: Int): Double = i
def plus(x: Double, y: Double): Double = x + y
def div(x: Double, y: Double): Double = x / y
}
}

object Calc {
def meandot[L, R, N](xs: Array[L], ys: Array[R])
(implicit t: Times[L, R, N], n: Num[N]): N = {
val total = (xs, ys).zipped.foldLeft(n.zero){
case(r, (x, y)) => n.plus(r, t.times(x, y))
}
n.div(total, n.fromInt(xs.length))
}

implicit class MeanDotImp[L, R](val marker: Calc.type) {
def meandot2[N](xs: Array[L], ys: Array[R])
(implicit t: Times[L, R, N], n: Num[N]): N = {
val total = (xs, ys).zipped.foldLeft(n.zero){
case(r, (x, y)) => n.plus(r, t.times(x, y))
}
n.div(total, n.fromInt(xs.length))
}
}
}

Answer

An alternative solution is similar to yours, but is a bit more straightforward: it first fixes the type parameter that you want to be able to set and then infers the other two. To achieve that we can declare a class with apply method:

class meandot[N] {

  def apply[L, R](xs: Array[L], ys: Array[R])
    (implicit t: Times[L, R, N], n: Num[N]): N = ??? // your implementation
}

Now, to avoid writing new meandot, we can define a method which just instantiates this class:

object Calc {

  def meandot[N]: meandot[N] = new meandot[N]
}

Elegance of this approach is arguable, but it's quite simple and doesn't involve implicits. Here's a usage demo:

scala> Calc.meandot(Array(1,2,3), Array(4,5,6))
res0: Int = 10

scala> Calc.meandot[Double](Array(1,2,3), Array(4,5,6))
res1: Double = 10.666666666666666