一种可能的方法是在应用操作之前统一两个向量的类型。通过这样做,Vector2[A] 上的操作总是可以将 Vector2[A] 作为参数。
类似的方法可用于乘法(参见下面的示例)。
使用从Vector2[A] 到Vector2[B] 的隐式转换(前提是Numeric[A] 和Numeric[B] 都存在并且您有隐式证据表明A 可以转换为B),您可以执行以下操作:
case class Vector2[A](val x: A, val y: A)(implicit n: Numeric[A]) {
import n.mkNumericOps
import scala.math.sqrt
def map[B: Numeric](f: (A => B)): Vector2[B] = Vector2(f(x), f(y))
def length = sqrt(x.toDouble * x.toDouble + y.toDouble * y.toDouble)
def unary_- = this.map(-_)
def +(that: Vector2[A]) = Vector2(x + that.x, y + that.y)
def -(that: Vector2[A]) = Vector2(x - that.x, y - that.y)
def *[B](s: B)(implicit ev: A => B, nb: Numeric[B]) = this.map(ev(_)).map(nb.times(_, s))
}
object Vector2 {
implicit def toV[A: Numeric, B: Numeric](v: Vector2[A])(
implicit ev: A => B // kindly provided by scala std library for all numeric types
): Vector2[B] = v.map(ev(_))
}
例子:
val x = Vector2(1, 2) //> x : Solution.Vector2[Int] = Vector2(1,2)
val y = Vector2(3.0, 4.0) //> y : Solution.Vector2[Double] = Vector2(3.0,4.0)
val z = Vector2(5L, 6L) //> z : Solution.Vector2[Long] = Vector2(5,6)
x + y //> res0: Solution.Vector2[Double] = Vector2(4.0,6.0)
y + x //> res1: Solution.Vector2[Double] = Vector2(4.0,6.0)
x + z //> res2: Solution.Vector2[Long] = Vector2(6,8)
z + x //> res3: Solution.Vector2[Long] = Vector2(6,8)
y + z //> res4: Solution.Vector2[Double] = Vector2(8.0,10.0)
z + y //> res5: Solution.Vector2[Double] = Vector2(8.0,10.0)
x * 2 //> res6: Solution.Vector2[Int] = Vector2(2,4)
x * 2.0 //> res7: Solution.Vector2[Double] = Vector2(2.0,4.0)
x * 2L //> res8: Solution.Vector2[Long] = Vector2(2,4)
x * 2.0f //> res9: Solution.Vector2[Float] = Vector2(2.0,4.0)
x * BigDecimal(2) //> res10: Solution.Vector2[scala.math.BigDecimal] = Vector2(2,4)
根据 Chris 在 cmets 中的要求,这是一个隐式转换链如何工作的示例
如果我们使用 scala -XPrint:typer 运行 scala REPL,我们可以明确地看到隐式在工作
比如
z + x
变成
val res1: Vector2[Long] = $line7.$read.$iw.$iw.$iw.z.+($iw.this.Vector2.toV[Int, Long]($line4.$read.$iw.$iw.$iw.x)(math.this.Numeric.IntIsIntegral, math.this.Numeric.LongIsIntegral, {
((x: Int) => scala.this.Int.int2long(x))
}));
翻译成更易读的术语是
val res: Vector2[Long] = z + toV[Int, Long](x){ i: Int => Int.int2long(i) }
^____________________________________________^
the result of this is a Vector[Long]
相反,x + z 变为
val res: Vector2[Long] = toV[Int, Long](x){ i: Int => Int.int2long(i) } + z
它的工作方式大致是这样的:
- 我们说
z: V[Long] + x: V[Int]
- 编译器发现有一个方法
+[Long, Long]
- 它看起来是从
V[Int] 到V[Long] 的转换
- 它找到
toV
- 它根据
toV 的要求查找从Int 到Long 的转换
- 它找到
Int.int2Long,即一个函数Int => Long
- 然后它可以使用
toV[Int, Long],即一个函数V[Int] => V[Long]
- 确实如此
x + toV(z)
如果我们这样做了x: V[Int] + z: V[Long]
- 编译器看到有一个方法
+[Int, Int]
- 它看起来是从
V[Long] 到V[Int] 的转换
- 它找到
toV
- 它根据
toV 的要求查找从Long 到Int 的转换
- 找不到!
- 它看到有一个方法
+[Long, Long]
我们回到前面例子的第 3 点
更新
正如在 cmets 中注意到的那样,在做的时候有一个问题
Vector(2.0, 1.0) * 2.0f
这几乎就是问题所在:
2.0f * 3.0 // 6.0: Double
还有
2.0 * 3.0f // 6.0: Double
所以不管参数是什么,当混合双精度和浮点数时,我们总是以双精度结尾。
不幸的是,我们需要A => B 的证据才能将向量转换为s 的类型,但有时我们实际上希望将s 转换为向量的类型。
我们需要处理这两种情况。第一种天真的方法可能是
def *[B](s: B)(implicit ev: A => B, nb: Numeric[B]): Vector[B] =
this.map(nb.times(ev(_), s)) // convert A to B
def *[B](s: B)(implicit ev: B => A, na: Numeric[A]): Vector[A] =
this.map(na.times(_, ev(s))) // convert B to A
整洁,对吧?太糟糕了,它不起作用:scala 在消除重载方法的歧义时不考虑隐式参数。我们必须按照here 的建议使用磁铁模式来解决这个问题。
case class Vector2[A](val x: A, val y: A)(implicit na: Numeric[A]) {
object ToBOrToA {
implicit def fromA[B: Numeric](implicit ev: A => B): ToBOrToA[B] = ToBOrToA(Left(ev))
implicit def fromB[B: Numeric](implicit ev: B => A): ToBOrToA[B] = ToBOrToA(Right(ev))
}
case class ToBOrToA[B: Numeric](e: Either[(A => B), (B => A)])
def *[B](s: B)(implicit ev: ToBOrToA[B], nb: Numeric[B]) = ev match {
case ToBOrToA(Left(f)) => Vector2[B](nb.times(f(x), s), nb.times(ev(y), s))
case ToBOrToA(Right(f)) => Vector2[A](na.times(x, f(s)), na.times(y, f(s))
}
}
我们只有一个* 方法,我们检查隐式参数ev 以了解是否必须将所有内容转换为向量的类型或s 的类型。
这种方法的唯一缺点是结果类型。 ev match { ... } 返回的东西是 B with A 的超类型,我仍然没有找到解决方法。
val a = x * 2.0 //> a : Solution.Vector2[_ >: Double with Int] = Vector2(2.0,4.0)
val b = y * 2 //> b : Solution.Vector2[_ >: Int with Double] = Vector2(6.0,8.0)