在Scala中实现泛型向量
我试图在Scala中实现一个通用(数学)向量,我遇到了几个问题,即如何正确地实现它: 1) 如何处理+和-使得对在Scala中实现泛型向量,scala,Scala,我试图在Scala中实现一个通用(数学)向量,我遇到了几个问题,即如何正确地实现它: 1) 如何处理+和-使得对向量[Int]和向量[Double]进行操作将返回向量[Double]?简而言之,我将如何进行数字类型的自动升级(最好利用Scala的自动升级)?因为只有当两个向量的类型相同时,使用隐式n:Numeric[T]才有效 2) 相关的,我应该如何定义一个*操作,使其接受任何数值类型,并返回正确数值类型的向量?也就是说,一个向量[Int]*2.0将返回一个向量[Double] 这是我当前的代
向量[Int]
和向量[Double]
进行操作将返回向量[Double]
?简而言之,我将如何进行数字类型的自动升级(最好利用Scala的自动升级)?因为只有当两个向量的类型相同时,使用隐式n:Numeric[T]
才有效
2) 相关的,我应该如何定义一个*操作,使其接受任何数值类型,并返回正确数值类型的向量?也就是说,一个向量[Int]*2.0
将返回一个向量[Double]
这是我当前的代码(其行为与我所希望的不同):
更新
经过深思熟虑,我决定接受Chris K的答案,因为它适用于我所问的所有情况,尽管类型类解决方案非常冗长(Scala中的数字类型是Byte、Short、Int、Long、Float、Double、BigInt、BigDecimal,这使得实现每对可能的类型之间的所有操作非常有趣)
我对这两个答案都投了较高的票,因为它们都是很好的答案。我真的希望加布里埃尔·佩特罗内拉的答案适用于所有可能的情况,哪怕只是因为这是一个非常优雅和深思熟虑的答案。我真的希望最终会有办法奏效。想到一些方法:
trait NumberLike[A,B,C] {
def plus(x: A, y: B): C
}
object NumberLike {
implicit object NumberLikeIntDouble extends NumberLike[Int,Double,Double] {
def plus(x: Int, y: Double): Double = x + y
}
implicit object NumberLikeDoubleInt extends NumberLike[Double,Int,Double] {
def plus(x: Double, y: Int): Double = x + y
}
implicit object NumberLikeIntInt extends NumberLike[Int,Int,Int] {
def plus(x: Int, y: Int): Int = x + y
}
}
case class Vector2[T](val x: T, val y: T) {
def +[B,C](that: Vector2[B])(implicit c:NumberLike[T,B,C]) : Vector2[C] = new Vector2[C](c.plus(this.x,that.x), c.plus(this.y,that.y))
}
val a = Vector2(1,2)
val b = Vector2(2.0,2.0)
a+a
a+b
b+a
向向量中添加更多运算符,如减法和除法,然后将它们添加到类似数字的特征中,然后使用上面的加号示例进行操作。一种可能的方法是在应用操作之前统一两个向量的类型。这样,对
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在评论中的要求,下面是一个隐式转换链如何工作的示例 如果我们使用
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]
更新 正如在评论中所注意到的,执行时有一个问题
Vector(2.0, 1.0) * 2.0f
这几乎就是问题所在:
2.0f * 3.0 // 6.0: Double
而且
2.0 * 3.0f // 6.0: Double
所以不管什么论点,当混合双倍和浮点数时,我们总是以双倍结束。
不幸的是,为了将向量转换为s
的类型,我们需要A=>B
的证据,但有时我们实际上想要将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在消除重载方法时不考虑隐含的参数。我们必须用磁铁模式来解决这个问题。 我们只有一个
*
方法,我们检查隐式参数ev
,以了解是否必须将所有内容转换为向量类型或s
类型
这种方法唯一的缺点是结果类型。ev match{…}
返回的东西是带有A的B的超类型,我还没有找到解决方法
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)
不错的方法,我喜欢。
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))
}
}
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)