任何尺寸的Scala矢量使用无形
我需要在n维欧几里德空间中测量距离,所以我必须创建多维向量,并且能够比较它们的尺寸并执行一些基本操作,如“或” – “.所以,我以为我会使用类型类型,如下所示:
Implementing a generic Vector in Scala 但是在投入大量时间之后,我仍然不明白如何实现这一点.我的意思是,我可以理解类型类后面的想法,可以使用它们,但不知道如何对它们应用无形的.提前感谢任何帮助,至少最简单的例子显示如何使用类型类与无形. 解决方法
第一步是要找到或定义一个类型类与您要支持的操作.
scala.math.Numeric 是一种可能性 – 它提供加法,减法等,但事实上,它也需要转换来自例如. Int意味着这可能不是正确的选择.像
algebra和
Spire这样的项目包括更合适的候选人.
定义类型类 为了保持简单,我们可以自己定义: trait VectorLike[A] { def dim: Int def add(x: A,y: A): A def subtract(x: A,y: A): A } (请注意,我使用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) = (x._1 - y._1,x._2 - y._2) } 现在我们可以这样使用这个实例: scala> implicitly[VectorLike[(Double,Double)]].add((0.0,0.0),(1.0,1.0)) res0: (Double,Double) = (1.0,1.0) 这是很冗长的,但它是有效的. 隐性操作类 定义一个隐式的“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) } 现在你可以写下列内容: 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,1.0) 这不是必需的 – 它只是一个方便的模式,你会经常看到. 一般实例 当我们的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) = (A.minus(x._1,A.minus(x._2,y._2)) } 请注意,我已经给数值类型类实例与通用类型(A)相同的名称.这是一种常见的惯例,对于类型需要单一类型类实例的方法,但并不是必需的 – 我们可以将其称为numericA或任何我们想要的. 现在我们可以使用数字实例的任何类型的元组使用我们的运算符: scala> (1,2) |+| (3,4) res3: (Int,Int) = (4,6) 这是一个很大的改进,但它仍然只是一个单一的矢量大小. 派生Shapeless的实例 我们还没有看到任何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))) } 这看起来很复杂,但它是基本的模式,你会看到任何时候你使用Shapeless进行通用派生.我不会在这里详细描述发生了什么,但是请参见我的博客文章here讨论了一个类似的例子. 现在我们可以这样写: scala> (1,2,3,4) |+| (5,6,7,8) res1: (Int,Int,Int) = (6,8,10,12) 或这个: scala> (0.0,0.0,2.0,3.0,4.0,5.0,6.0) res4: (Double,Double,-2.0,-3.0,-4.0,-5.0,-6.0) 它只是工作. 其他向量表示 我一直在使用元组来表示上述所有示例中的多维向量,但是您也可以使用Shapeless的Sized,它是一个在类型级别编码其长度的均匀集合.您可以为Sized类型提供VectorLike实例,而不是(或除了)元组实例,而不对VectorLike本身进行任何更改. 你应该选择哪种代表取决于一些因素.元组很容易编写,对大多数Scala开发人员来说都是自然的,但如果您需要22维度的向量,那么它们就不会工作. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |