[英]Implementing a generic Vector in Scala
I'm trying to implement a generic (mathematical) vector in Scala, and I'm running into a couple of issues of how to do it properly: 我正在尝试在Scala中实现一个通用(数学)向量,我遇到了一些如何正确执行它的问题:
1) How do you handle + and - such that operating on a Vector[Int]
and a Vector[Double]
would return a Vector[Double]
? 1)你如何处理+和 - 这样在Vector[Int]
和Vector[Double]
会返回Vector[Double]
? In short, how would I go about doing auto promotion of numeric types (preferably taking advantage of Scala's auto promotion)? 简而言之,我将如何进行数字类型的自动推广(最好利用Scala的自动推广)? Because using implicit n: Numeric[T]
only works if the types of both vectors are the same. 因为使用implicit n: Numeric[T]
仅在两个向量的类型相同时才有效。
2) Related, how should I define a * operation such that it takes in any Numeric type, and return a vector of the right numeric type? 2)相关的,我应该如何定义一个*操作,使其接受任何数值类型,并返回一个正确的数字类型的向量? That is, a Vector[Int] * 2.0
would return a Vector[Double]
. 也就是说, Vector[Int] * 2.0
将返回Vector[Double]
。
This is my current code (which doesn't behave as I would want it): 这是我当前的代码(它不像我想要的那样):
case class Vector2[T](val x: T, val y: T)(implicit n: Numeric[T]) {
import n._
def length = sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble())
def unary_- = new Vector2(-x, -y)
def +(that: Vector2) = new Vector2(x + that.x, y + that.y)
def -(that: Vector2) = new Vector2(x - that.x, y - that.y)
def *(s: ???) = new Vector2(x * s, y * s)
}
After a lot of thought, I've decided to accept Chris K's answer, because it works for all the situations I've asked about, despite the verbosity of the type class solution (the numeric types in Scala are Byte, Short, Int, Long, Float, Double, BigInt, BigDecimal, which makes for a very fun time implementing all the operations between each possible pair of types). 经过深思熟虑之后,我决定接受Chris K的答案,因为它适用于我所提出的所有情况,尽管类型类解决方案的冗长(Scala中的数字类型是Byte,Short,Int, Long,Float,Double,BigInt,BigDecimal,这使得在每个可能的类型对之间实现所有操作非常有趣。
I've upvoted both answers, because they're both excellent answers. 我赞同这两个答案,因为它们都是很好的答案。 And I really wish Gabriele Petronella's answer worked for all possible scenarios, if only because it's a very elegant and consise answer. 我真的希望Gabriele Petronella的答案适用于所有可能的场景,只是因为它是一个非常优雅和明确的答案。 I do hope there'll be some way that it'll work eventually. 我希望有一些方法可以最终发挥作用。
A possible approach is to unify the type of the two vectors before applying the operation. 一种可能的方法是在应用操作之前统一两个向量的类型。 By doing so, operations on Vector2[A]
can alwyas take a Vector2[A
] as parameter. 通过这样做, Vector2[A]
可以将Vector2[A
]作为参数。
A similar approach can be used for multiplication (see the example below). 类似的方法可用于乘法(参见下面的例子)。
Using an implicit conversion from Vector2[A]
to Vector2[B]
(provided that Numeric[A]
and Numeric[B]
both exist and that you have implicit evidence that A
can be converted to B
), you can do: 使用从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(_))
}
examples: 例子:
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)
As per Chris' request in the comments, here's an example of how the implicit conversions chain work 根据Chris在评论中的请求,这里是隐式转换链如何工作的一个例子
If we run the scala REPL with scala -XPrint:typer
, we can see the implicits at work explicitly For instance 如果我们使用scala -XPrint:typer
运行scala REPL,我们可以看到显式工作中的implicits例如
z + x
becomes 变
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))
}));
which translated to more readable terms is 翻译成更易读的术语是
val res: Vector2[Long] = z + toV[Int, Long](x){ i: Int => Int.int2long(i) }
^____________________________________________^
the result of this is a Vector[Long]
Conversely, x + z
becomes 相反, x + z
变为
val res: Vector2[Long] = toV[Int, Long](x){ i: Int => Int.int2long(i) } + z
The way it works is roughly this: 它的工作方式大致如下:
z: V[Long] + x: V[Int]
我们说z: V[Long] + x: V[Int]
+[Long, Long]
编译器看到有一个方法+[Long, Long]
V[Int]
to V[Long]
它看起来从V[Int]
到V[Long]
toV
它找到toV
Int
to Long
as required by toV
它根据toV
要求寻找从Int
到Long
的转换 Int.int2Long
, ie a function Int => Long
它找到Int.int2Long
,即函数Int => Long
toV[Int, Long]
ie a function V[Int] => V[Long]
然后它可以使用toV[Int, Long]
即函数V[Int] => V[Long]
x + toV(z)
它做x + toV(z)
in case we do instead x: V[Int] + z: V[Long]
如果我们改为x: V[Int] + z: V[Long]
+[Int, Int]
编译器看到有一个方法+[Int, Int]
V[Long]
to V[Int]
它看起来从V[Long]
到V[Int]
toV
它找到toV
Long
to Int
as required by toV
它根据toV
要求寻找从Long
到Int
的转换 +[Long, Long]
它看到有一种方法+[Long, Long]
and we're back to point 3 of the previous example 我们回到上一个例子的第3点
As noticed in the comments, there's a problem when doing 正如评论中所注意到的,这样做时会出现问题
Vector(2.0, 1.0) * 2.0f
This is pretty much the issue: 这几乎是个问题:
2.0f * 3.0 // 6.0: Double
but also 但是也
2.0 * 3.0f // 6.0: Double
So it doesn't matter what's the argument, when mixing doubles and floats we always end up with a double. 所以这个论点是什么并不重要,当混合双打和花车时我们总是以双倍结束。 Unfortunately we're requiring evidence of A => B
in order to convert the vector to the type of s
, but sometimes we actually want to convert s
to the type of the vector. 不幸的是,为了将向量转换为s
的类型,我们需要A => B
证据,但有时我们实际上想要将s
转换为向量的类型。
We need to handle the two cases. 我们需要处理这两个案件。 The first naive approach could be 第一个天真的方法可能是
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
Neat, right? 干净吧? Too bad it doesn't work: scala does not consider implicit arguments when disambiguating overloaded methods. 太糟糕了它不起作用:在消除重载方法的歧义时,scala不考虑隐式参数。 We have to work around this, using the magnet pattern, as suggested 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))
}
}
We have only one *
method, and we inspect the implicit parameter ev
to know whether we have to convert everything to the type of the vector or to the type of s
. 我们只有一个*
方法,我们检查隐式参数ev
以了解我们是否必须将所有内容转换为向量的类型或s
的类型。
The only drawback of this approach is the result type. 这种方法的唯一缺点是结果类型。 ev match { ... }
returns something that it's supertype of B with A
, and I still haven't found a workaround for it. ev match { ... }
返回的东西是B with A
的超类型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)
A few approaches come to mind: 我想到了一些方法:
Using Type Classes directly: 直接使用类型类:
This approach is somewhat verbose the first time that you create it, as one has to create implicit classes for each combination of values that one wants to support. 这种方法在您第一次创建时有点冗长,因为必须为每个想要支持的值组合创建隐式类。 But the approach is sound. 但这种方法很合理。 More details about type classes can be read here 有关类型类的更多详细信息,请参见此处
If you want to copy and paste the following code into the scala REPL, be sure to enter ':paste' first. 如果要将以下代码复制并粘贴到scala REPL中,请务必先输入':paste'。 Otherwise the relationship between the trait and the companion object will not be picked up and the implicit will not be found when one enters 'a+b'. 否则,将不会拾取特征与伴随对象之间的关系,并且当输入“a + b”时将不会发现隐式。
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
To add more operators to the vector, like subtraction and divide then add them to the NumberLike trait and follow it through using the plus example above. 要向矢量添加更多运算符,例如减法和除法,然后将它们添加到NumberLike特征并使用上面的加号示例进行跟踪。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.