Eastsun
Eastsun

Reputation: 18869

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))
    }
  }
}

Upvotes: 3

Views: 294

Answers (1)

laughedelic
laughedelic

Reputation: 6460

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

Upvotes: 3

Related Questions