【问题标题】:Scala: abstracting over a path-dependent type in impilicit parameterScala:在隐式参数中抽象路径相关类型
【发布时间】:2017-04-13 12:40:00
【问题描述】:

假设我有一堂课:

abstract class NumericCombine[A:Numeric,B:Numeric]{
        type AB <: AnyVal
    }

我想定义一个返回NumericCombine[A,B].AB 类型值的函数。例如:

def plus[A: Numeric,B:Numeric](x: A, y: B): NumericCombine[A,B].AB

但编译器不允许我在加号中引用.AB

仅供参考,this 是这个问题的上下文。

我要提供:

implicit object IntFloat extends NumericCombine[Int,Float]{override type AB = Float}
implicit object FloatInt extends NumericCombine[Float,Int]{override type AB = Float}

和它的其他 44 个朋友 (7*6-2),这样我就可以定义我的 plus 如下:

def plus[A: Numeric,B:Numeric](x: A, y: B): NumericCombine[A,B].AB =
{
type AB = Numeric[NumericCombine[A,B].AB]
implicitly[AB].plus(x.asInstanceOf[AB],y.asInstanceOf[AB])
}

plus(1f,2)//=3f
plus(1,2f)//=3f

我知道 Scala 中的值转换允许我定义

def plus[T](a: T, b: T)(implicit ev:Numeric[T]): T = ev.plus(a,b)

并按照here 的建议实现上述行为,但由于我想将此函数用作更大函数的一部分(在此问题的上下文中提到的链接中进行了描述),我需要对函数进行参数化AB

更新:

我在这方面取得了一些不错的进展。

我的NumericCombine 现在看起来像这样:

abstract class NumericCombine[A: Numeric, B: Numeric] {
        type AB <: AnyVal

        def fromA(x: A): AB
        def fromB(y: B): AB

        val numeric: Numeric[AB]

        def plus(x: A, y: B): AB = numeric.plus(fromA(x), fromB(y))
        def minus(x: A, y: B): AB = numeric.minus(fromA(x), fromB(y))
        def times(x: A, y: B): AB = numeric.times(fromA(x), fromB(y))
    }

而我的 plus 函数看起来像:

def plus[A: Numeric, B: Numeric](x: A, y: B)(implicit ev:NumericCombine[A,B])
        : ev.AB = ev.plus(x, y)

需要plus 的加权平均函数最终变得有点复杂:

def accumulateWeightedValue[A: Numeric,B: Numeric]
            (accum: (A, NumericCombine[A, B]#AB), ValueWithWeight: (A, B))
            (implicit combine: NumericCombine[A, B], timesNumeric: Numeric[NumericCombine[A, B]#AB])
            :(A,NumericCombine[A, B]#AB)=

这是一个接受(A,AB),(A,B) 并返回(A,AB) 的函数。我在 weightedSum 内部使用它,它只是聚合在这个上面:

def weightedSum[A: Numeric,B: Numeric](weightedValues: GenTraversable[(A, B)])
(implicit numericCombine: NumericCombine[A, B], plusNumeric: Numeric[NumericCombine[A, B]#AB])
: (A, NumericCombine[A, B]#AB)

现在,这编译得很好。第二个隐式参数似乎确实有问题。即 Numeric[AB] 当我使用隐含值运行它时,比如 NumericCombine[Int,Float] 存在。它给了我:

找不到参数 plusNumeric 的隐含值: 数值[NumericCombine[Int,Float]#AB]

请注意,在 NumericCombine 中,我有一个 Numeric[AB] 应该可用于隐式查找。存储在本地,在[Int,Float]的情况下:

val lst: Seq[(Int, Float)] =List((1,3f),(1,4f))
implicit val num: Numeric[Float] = IntFloat.numeric //IntFloat extends NumericCombine[Int,Float]
weightedSum(lst)

在调用需要它的函数之前在局部变量中似乎没有任何影响。那么为什么会被隐式系统拾取呢。

【问题讨论】:

  • NumericCombine[A,B]#AB 视为AB 的所有NumericCombine[A,B] 实例可能 存在的通用类型。所以accum 的类型错误。
  • 我明白了。那么有什么方法可以抽象出这些 AB 吗?允许其他函数使用这个泛型加号?
  • 你说accumulateWeightedValueweightedSum内部使用。所以只要让它成为一个本地方法,你就可以使用numericCombine.AB。你不应该在AB 上需要plusNumericNumeric 约束。
  • @AlexeyRomanov 你是说使用隐式作为函数的参数会阻止你的函数成为“一流的函数”,我应该只在本地使用它们吗?
  • 不,我是说你应该在这种特定情况下这样做,因为accum 的类型应该取决于combine

标签: scala type-alias


【解决方案1】:

随便用

def plus[A: Numeric,B:Numeric](x: A, y: B): NumericCombine[A,B]#AB

注意#(散列)而不是.(点)。这称为“类型投影”。点表示法称为“路径依赖类型”。我告诉你这些名字是为了让你可以很容易地用谷歌搜索更多信息。简单地说,# 用于访问类/特征中的类型,而 .用于从对象/值访问类型。

例子:

trait Foo {
    type T   
}

val fooObj: Foo = new Foo {
    type T = Int
}

type t1 = fooObj.T
type t2 = Foo#T

【讨论】:

  • 不幸的是,我认为您不能“只使用”它来解决这个特定问题:plus[Int, Float] 的返回类型将是 NumericCombine[Int, Float]#AB 而不是 Float
  • 这让我很困惑。我现在已经定义了 def plusAB[A: Numeric, B: Numeric](x: A, y: B) (implicit ev: NumericCombine[A, B]): ev.AB = ev.plus(x, y) ,它工作得很好。现在,如果我创建另一个具有完全相同签名的函数,但重定向到此函数,即:plusAB2[..](...)=plusAB(x,y) 我得到:NumericCombine[A,B]#AB 类型的表达式不符合预期的类型:ev。 AB 我期待这会起作用吗?
  • 嗯,实际上阅读您的答案文本开始让我了解问题所在。我现在已经用一些进展和一个问题更新了我的问题,我认为这是我上面描述的另一种表现。谷歌搜索“Scala 隐式类型投影”带来了一些我不太理解的东西。
【解决方案2】:

* 2017 年 4 月 18 日:根据作者的最新代码更新 *

* 2017 年 4 月 19 日 *

  • NumericCombine#Implicits方便
  • 删除AnyVal 约束以支持任何Numeric 类型,例如BigInt
  • 重构NumericCombine

你需要Aux pattern:

import scala.collection.GenSeq

trait NumericCombine[A, B] {
  type AB

  def fromA(x: A): AB

  def fromB(y: B): AB

  val numericA: Numeric[A]
  val numericB: Numeric[B]
  val numericAB: Numeric[AB]

  // For convenience, caller can 'import combine.Implicits._'
  // to bring the Numeric's into the current scope
  object Implicits {
    implicit def implicitNumericA = numericA

    implicit def implicitNumericB = numericB

    implicit def implicitNumericAB = numericAB
  }

  def plus(x: A, y: B): AB = numericAB.plus(fromA(x), fromB(y))

  def minus(x: A, y: B): AB = numericAB.minus(fromA(x), fromB(y))

  def times(x: A, y: B): AB = numericAB.times(fromA(x), fromB(y))
}

object NumericCombine {
  type Aux[A, B, _AB] = NumericCombine[A, B] {
    type AB = _AB
  }

  private def combine[A, B, _AB](fa: A => _AB, fb: B => _AB)
                               (implicit
                                _numericA: Numeric[A],
                                _numericB: Numeric[B],
                                _numericAB: Numeric[_AB]
                               ): NumericCombine[A, B] = new NumericCombine[A, B] {
      override type AB = _AB

      override def fromA(x: A): AB = fa(x)

      override def fromB(y: B): AB = fb(y)

      override val numericA: Numeric[A] = _numericA
      override val numericB: Numeric[B] = _numericB
      override val numericAB: Numeric[AB] = _numericAB
    }

  implicit lazy val IntFloat  = combine[Int, Float, Float](_.toFloat, identity)
  implicit lazy val BigIntBigDecimal  = combine[BigInt, BigDecimal, BigDecimal](i => BigDecimal(i), identity)

}

implicit class ValuesWithWeight[A, B](val weightedValue: (A, B)) {
  def weight: A = weightedValue._1

  def value: B = weightedValue._2
}

def weightedSum[A, B, AB]
(valuesWithWeight: GenSeq[(A, B)])
(implicit combine: NumericCombine.Aux[A, B, AB]):
(A, AB) = {

  import combine.Implicits._

  val z: (A, AB) =
    (combine.numericA.zero, combine.numericAB.zero)

  def accumulateWeightedValue(accum: (A, AB), valueWithWeight: (A, B)): (A, AB) = {
    val weightedValue = combine.times(valueWithWeight.weight, valueWithWeight.value)
    (
      combine.numericA.plus(accum.weight, valueWithWeight.weight),
      combine.numericAB.plus(accum.value, weightedValue)
    )
  }

  valuesWithWeight.aggregate(z)(
    accumulateWeightedValue,
    // dataOps.tuple2.plus[A,AB]
    {
      case ((a1, ab1), (a2, ab2)) =>
        (combine.numericA.plus(a1, a2) ->
          combine.numericAB.plus(ab1, ab2))
    }
  )
}

weightedSum(Seq(1 -> 1.5f, 2 -> 1f, 3 -> 1.7f))
weightedSum(Seq(BigInt(1) -> BigDecimal("1.5"), BigInt(2) -> BigDecimal("1"), BigInt(3) -> BigDecimal("1.7")))

【讨论】:

  • 有趣!因此,据我了解,您的建议是:1)使用附加的AB 类型参数参数化“私有”函数,并基于Aux 模式构造正确的Numeric[A,B]。我仍然不确定如何在我的“公共”函数中使用利用 Aux 参数的函数,该函数仅采用 AB。即我想在weightedSum 中使用accumulateWeightedValue。将 accumulateWeightedValue[A,B,combine.AB] 传递给聚合给了我:具有依赖类型的 scala 方法无法转换为函数值
  • 嗨@ShS,关键是您需要为类型推断声明 AB 类型参数,然后隐式查找才能正常工作。您可以访问“辅助模式”上的链接了解更多详情。
  • 我已经用你的代码的工作版本更新了我的答案。还稍微简化了代码。看看是不是你需要的。
  • 太棒了。这行得通。我已经修改了您的解决方案并稍微重构了它。看这里:pastebin.com/69YCusc8 它只有一个问题。如果要将数字参数传递给其他函数,则必须提供显式隐式参数。有没有办法在 NumericCombine 对象中做一些魔术,比如 AUX 技术,将 numericA、numericB、numericAB 变量带入时代的“隐式范围”,加上或 addTuples 函数调用? [这是一个完整的工作演示:gist.github.com/ShahOdin/9b3a5326c7ee355c1cbe76e8bfb2a97c]
【解决方案3】:

@slouc 的答案的替代方案是

def plus[A, B](x: A, y: B)(implicit ev: NumericCombine[A, B]): ev.AB

我也会增强NumericCombine

trait NumericCombine[A, B] {
  type AB <: AnyVal
  def fromA(a: A): AB
  def fromB(b: B): AB
  val num: Numeric[AB]
}

abstract class NumericCombineImpl[A, B, R](implicit val num: Numeric[R], f1: A => R, f2: B => R) {
  type AB = R
  def fromA(a: A) = f1(a)
  def fromB(b: B) = f2(b)
}

implicit object IntFloat extends NumericCombineImpl[Int,Float,Float]
...

这将允许实际实现plus,不需要强制转换:

def plus[A, B](x: A, y: B)(implicit ev: NumericCombine[A, B]): ev.AB = 
  ev.num.plus(ev.fromA(x), ev.fromB(y))

【讨论】:

  • ha 我实际上正在做这件事,因为最初的计划变得乏味。此外,我决定在 NumericCombine 中移动加倍函数等。它只是更少的类和更少的隐含参数。
  • 这就是我最终实现加号的方式。 pastebin.com/ne0KwfnK 我很想得到您的反馈。我有点不确定是否要求 R 成为 AnyVal 的子类型。
  • 另见:stackoverflow.com/questions/43392597/… 我基本上似乎无法在依赖类型中使用您建议的 num 变量,所以我一直在为我的函数添加第二个隐式函数,而我希望以某种方式从 combine 访问 num。
猜你喜欢
  • 2012-12-27
  • 1970-01-01
  • 2012-06-22
  • 2011-11-19
  • 2011-03-20
  • 2018-08-01
  • 2016-03-12
  • 2011-08-29
  • 1970-01-01
相关资源
最近更新 更多