【问题标题】:How to implement generic function in Scala with two argument types?如何在 Scala 中使用两种参数类型实现泛型函数?
【发布时间】:2015-06-11 02:29:29
【问题描述】:

我想在 Scala 中实现一个函数,计算两个数字序列的点积,如下所示

val x = Seq(1,2,3.0)
val y = Seq(4,5,6)
val z = (for (a <- x; b <- y) yield a*b).sum
scala> z  : Double = 90.0

val x = Seq(1,2,3)
val y = Seq(4,5,6)
val z = (for (a <- x; b <- y) yield a*b).sum
scala> z  : Int = 90

请注意,如果两个序列的类型不同,则结果为 Double。如果两个序列属于同一类型(例如 Int),则结果为 Int。

我想出了两个替代方案,但都不符合上面定义的要求。

备选方案#1:

def dotProduct[T: Numeric](x: Seq[T], y: Seq[T]): T = (for (a <- x; b <- y) yield implicitly[Numeric[T]].times(a, b)).sum

这会返回与输入相同类型的结果,但不能采用两种不同的类型。

备选方案#2:

def dotProduct[A, B](x: Seq[A], y: Seq[B])(implicit nx: Numeric[A], ny: Numeric[B]) = (for (a <- x; b <- y) yield nx.toDouble(a)*ny.toDouble(b)).sum

这适用于所有数字序列。然而,它总是返回一个 Double,即使这两个序列是 Int 类型。

非常感谢任何建议。

附言我上面实现的功能不是“点积”,而只是两个序列的乘积之和。感谢丹尼尔指出这一点。

备选方案#3(略好于备选方案#1 和#2):

def sumProduct[T, A <% T, B <% T](x: Seq[A], y: Seq[B])(implicit num: Numeric[T]) = (for (a <- x; b <- y) yield num.times(a,b)).sum

sumProduct(Seq(1,2,3), Seq(4,5,6))  //> res0: Int = 90
sumProduct(Seq(1,2,3.0), Seq(4,5,6))  //> res1: Double = 90.0
sumProduct(Seq(1,2,3), Seq(4,5,6.0))  // Fails!!!

不幸的是,在 Scala 2.10 中,视图绑定功能(例如“

【问题讨论】:

  • 你总是希望它返回 Int 吗?
  • 不,他想返回适合这两种类型的最少通用类型

标签: scala generics


【解决方案1】:

您可以创建一个表示促销规则的类型类:

trait NumericPromotion[A, B, C] {
  def promote(a: A, b: B): (C, C)
}

implicit object IntDoublePromotion extends NumericPromotion[Int, Double, Double] {
  def promote(a: Int, b: Double): (Double, Double) = (a.toDouble, b)
}

def dotProduct[A, B, C]
              (x: Seq[A], y: Seq[B])
              (implicit numEv: Numeric[C], promEv: NumericPromotion[A, B, C])
              : C = {
  val foo = for {
    a <- x
    b <- y
  } yield {
    val (pa, pb) = promEv.promote(a, b)
    numEv.times(pa, pb)
  }

  foo.sum
}

dotProduct[Int, Double, Double](Seq(1, 2, 3), Seq(1.0, 2.0, 3.0))

我的 typeclass-fu 不足以消除对dotProduct 的调用中的显式类型参数,我也无法弄清楚如何避免方法内的val foo;内联foo 导致编译器错误。我将此归结为没有真正内化隐式解析规则。也许其他人可以让你走得更远。

还值得一提的是,这是定向的;你无法计算dotProduct(Seq(1.0, 2.0, 3.0), Seq(1, 2, 3))。但这很容易解决:

implicit def flipNumericPromotion[A, B, C]
                                 (implicit promEv: NumericPromotion[B, A, C])
                                 : NumericPromotion[A, B, C] = 
  new NumericPromotion[A, B, C] {
    override def promote(a: A, b: B): (C, C) = promEv.promote(b, a)
  }

还值得一提的是,您的代码不计算点积。 [1, 2, 3][4, 5, 6] 的点积是4 + 10 + 18 = 32

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    相关资源
    最近更新 更多