Scala:对非强制参数中与路径相关的类型进行抽象 [英] Scala: abstracting over a path-dependent type in impilicit parameter
问题描述
让我说一堂课
abstract class NumericCombine[A:Numeric,B:Numeric]{
type AB <: AnyVal
}
我想定义一个返回类型为NumericCombine[A,B].AB
的值的函数.例如:
I want to define a function that returns a value of type NumericCombine[A,B].AB
. for instance:
def plus[A: Numeric,B:Numeric](x: A, y: B): NumericCombine[A,B].AB
,但是编译器不允许我加上.AB
.
but the compiler doesn't let me reference .AB
in plus.
FYI,这是此问题的上下文.
FYI, this is the context of this question.
我想提供:
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
:
and its other 44 friends (7*6-2) so that I can define my plus
as below:
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中的值转换使我可以定义
I am aware of the fact that value conversions in Scala allows me to define
def plus[T](a: T, b: T)(implicit ev:Numeric[T]): T = ev.plus(a,b)
并按照此处的建议实现上述行为,但是由于我想将此功能用作更大功能的一部分(在提到该问题的上下文中的链接中进行了描述),我需要同时使用A
和B
对该函数进行参数化.
and achieve the behaviour above as suggested here, but since I want to use this function as part of a bigger function (which is described in the link mentioned as the context of this question), I need to parametrize the function with both A
and B
.
更新:
我在这方面取得了一些不错的进步.
I made some good progress with this.
我的NumericCombine
现在看起来像这样:
My NumericCombine
now looks like this:
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))
}
我的加号"功能如下:
def plus[A: Numeric, B: Numeric](x: A, y: B)(implicit ev:NumericCombine[A,B])
: ev.AB = ev.plus(x, y)
需要plus
的加权平均函数最终变得更加复杂:
The weighted average function requiring plus
ended up becoming a bit more complicated:
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
内部内部使用它,仅对此进行汇总:
this is a function that takes (A,AB),(A,B)
and returns (A,AB)
. I use it internally inside weightedSum
which just aggregates over this:
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]
)运行它时.它给了我
Now, this compiles fine. It does seem to have a problem with the second implicit parameter. ie Numeric[AB] when I run it with implicit values for say NumericCombine[Int,Float]
present. It gives me:
找不到参数plusNumeric的隐式值: 数值[NumericCombine [Int,Float] #AB]
could not find implicit value for parameter plusNumeric: Numeric[NumericCombine[Int,Float]#AB]
请注意,在NumericCombine中,我有一个Numeric [AB],应该可用于隐式查找.如果是[Int,Float]
,则将其存储在本地:
note that in NumericCombine, I have a Numeric[AB] which should be available for implicit look-up. storing it locally, in the case of [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)
在调用需要它的函数之前,在局部变量中
似乎没有任何影响.那么为什么它被隐式系统拾取.
in a local variable before invoking the function needing it doesn't seem to have any impact. So why is it being picked up by the implicit system.
推荐答案
* 2017年4月18日:基于作者的最新代码进行了更新*
* 2017年4月19日*
- 添加
NumericCombine#Implicits
为方便起见 - 删除
AnyVal
约束以支持任何Numeric
类型,例如BigInt
- 重构
NumericCombine
- Add
NumericCombine#Implicits
for convinence - Remove
AnyVal
constraints to support anyNumeric
types e.g.BigInt
- Refactor
NumericCombine
您需要辅助模式:>
You need 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")))
这篇关于Scala:对非强制参数中与路径相关的类型进行抽象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!