Tra*_*own 30
第一步是查找或定义包含您要支持的操作的类型类.scala.math.Numeric是一种可能性 - 它提供了加法,减法等,但事实上它还需要转换和转换,例如Int意味着它可能不是正确的选择.代数和Spire等项目包括更合适的候选人.
为了简单起见,我们可以定义自己的:
trait VectorLike[A] {
def dim: Int
def add(x: A, y: A): A
def subtract(x: A, y: A): A
}
Run Code Online (Sandbox Code Playgroud)
(请注意,我使用Like后缀来避免与集合库的冲突Vector.这是您有时会看到的命名约定,但在任何情况下都不需要在Scala中使用类型类 - 在此上下文中更抽象数学名称喜欢Monoid或Group更常见.)
接下来,我们可以为双精度二维矢量定义一个类型类实例,例如:
implicit val doubleVector2D: VectorLike[(Double, Double)] =
new VectorLike[(Double, Double)] {
def dim: Int = 2
def add(x: (Double, Double), y: (Double, Double)): (Double, Double) =
(x._1 + y._1, x._2 + y._2)
def subtract(x: (Double, Double), y: (Double, Double)): (Double, Double) =
(x._1 - y._1, x._2 - y._2)
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以像这样使用这个实例:
scala> implicitly[VectorLike[(Double, Double)]].add((0.0, 0.0), (1.0, 1.0))
res0: (Double, Double) = (1.0,1.0)
Run Code Online (Sandbox Code Playgroud)
这非常冗长,但它确实有效.
定义一个隐式的"ops"类通常很方便,使它看起来像一个带有类型类实例的类型的值具有从类类操作派生的方法:
implicit class VectorLikeOps[A: VectorLike](wrapped: A) {
def dim: Int = implicitly[VectorLike[A]].dim
def |+|(other: A): A = implicitly[VectorLike[A]].add(wrapped, other)
def |-|(other: A): A = implicitly[VectorLike[A]].subtract(wrapped, other)
}
Run Code Online (Sandbox Code Playgroud)
现在您可以编写以下内容:
scala> (0.0, 0.0) |-| (1.0, 1.0)
res1: (Double, Double) = (-1.0,-1.0)
scala> (0.0, 0.0) |+| (1.0, 1.0)
res2: (Double, Double) = (1.0,1.0)
Run Code Online (Sandbox Code Playgroud)
这不是必需的 - 它只是您经常看到的一种方便的模式.
虽然我们的doubleVector2D实例有效,但为每种数字类型定义这些实例都很烦人且是样板文件.我们可以通过使用以下方法为任何二维数字类型向量提供实例来改善这种情况scala.math.Numeric:
implicit def numericVector2D[A](implicit A: Numeric[A]): VectorLike[(A, A)] =
new VectorLike[(A, A)] {
def dim: Int = 2
def add(x: (A, A), y: (A, A)): (A, A) =
(A.plus(x._1, y._1), A.plus(x._2, y._2))
def subtract(x: (A, A), y: (A, A)): (A, A) =
(A.minus(x._1, y._1), A.minus(x._2, y._2))
}
Run Code Online (Sandbox Code Playgroud)
请注意,我给Numeric类型类实例的名称与泛型类型(A)相同.对于类型需要单个类型类实例的方法,这是常见的约定,但根本不需要 - 我们可以调用它numericA或我们想要的任何其他类型.
现在我们可以在任何类型的元组上使用我们的运算符Numeric:
scala> (1, 2) |+| (3, 4)
res3: (Int, Int) = (4,6)
Run Code Online (Sandbox Code Playgroud)
这是一个很大的改进,但它仍然只适用于单个矢量大小.
我们还没有见过任何Shapeless,但现在我们想要抽象出元组,这正是我们所需要的.我们可以重写我们的通用实例来处理任意数值类型的元组.我们有很多方法可以写这个,但以下是我的开始:
import shapeless._
trait HListVectorLike[L <: HList] extends VectorLike[L] { type El }
object HListVectorLike {
type Aux[L <: HList, A] = HListVectorLike[L] { type El = A }
implicit def vectorLikeHNil[A]: Aux[HNil, A] =
new HListVectorLike[HNil] {
type El = A
def dim: Int = 0
def add(x: HNil, y: HNil): HNil = HNil
def subtract(x: HNil, y: HNil): HNil = HNil
}
implicit def vectorLikeHCons[T <: HList, A](implicit
numeric: Numeric[A],
instT: Aux[T, A]
): Aux[A :: T, A] = new HListVectorLike[A :: T] {
type El = A
def dim: Int = instT.dim + 1
def add(x: A :: T, y: A :: T): A :: T =
numeric.plus(x.head, y.head) :: instT.add(x.tail, y.tail)
def subtract(x: A :: T, y: A :: T): A :: T =
numeric.minus(x.head, y.head) :: instT.subtract(x.tail, y.tail)
}
}
implicit def numericVector[P, Repr <: HList](implicit
gen: Generic.Aux[P, Repr],
inst: HListVectorLike[Repr]
): VectorLike[P] = new VectorLike[P] {
def dim: Int = inst.dim
def add(x: P, y: P): P = gen.from(inst.add(gen.to(x), gen.to(y)))
def subtract(x: P, y: P): P = gen.from(inst.subtract(gen.to(x), gen.to(y)))
}
Run Code Online (Sandbox Code Playgroud)
这看起来很复杂,但是,无论何时使用Shapeless进行泛型推导,基本模式都是你会看到的.我不会在这里详细描述正在发生的事情,但请参阅我的博客文章,以便讨论类似的例子.
现在我们可以这样写:
scala> (1, 2, 3, 4) |+| (5, 6, 7, 8)
res1: (Int, Int, Int, Int) = (6,8,10,12)
Run Code Online (Sandbox Code Playgroud)
或这个:
scala> (0.0, 0.0, 0.0, 0.0, 0.0, 0.0) |-| (1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
res4: (Double, Double, Double, Double, Double, Double) = (-1.0,-2.0,-3.0,-4.0,-5.0,-6.0)
Run Code Online (Sandbox Code Playgroud)
它只是有效.
我一直在使用元组来表示上面所有例子中的多维向量,但你也可以使用Shapeless's Sized,它是一个同类集合,在类型级别对其长度进行编码.您可以为元组提供VectorLike实例,Sized而不是(或除了)元组实例,而不对其VectorLike自身进行任何更改.
您应该选择哪种表示取决于许多因素.元组很容易编写,并且对于大多数Scala开发人员来说看起来很自然,但是如果你需要超过22维的向量,它们就无法工作.