[英]Scala vector of any dimension using shapeless
我需要測量n維歐幾里德空間中的距離,因此我必須創建多維向量並能夠比較它們的維度並執行一些基本操作,如“+”或“ - ”。 所以,我想我會使用類型類+無形,如下所示:
但是在投入了大量時間之后,我仍然無法理解如何實現這一點。 我的意思是,我可以理解類型類背后的想法可以使用它們,但不知道如何對它們應用無形。 提前感謝任何幫助,比如至少最簡單的示例顯示如何使用無形的類型類。
第一步是查找或定義包含您要支持的操作的類型類。 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
}
(請注意,我使用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)
}
現在我們可以像這樣使用這個實例:
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, Double) = (1.0,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), y: (A, A)): (A, A) =
(A.minus(x._1, y._1), A.minus(x._2, y._2))
}
請注意,我已經為Numeric
類型類實例指定了與泛型類型( A
)相同的名稱。 對於類型需要單個類型類實例的方法,這是一種常見的約定,但根本不需要 - 我們可以將其稱為numericA
或我們想要的任何其他類型。
現在我們可以在任何具有Numeric
實例的類型元組上使用我們的運算符:
scala> (1, 2) |+| (3, 4)
res3: (Int, Int) = (4,6)
這是一個很大的改進,但它仍然只適用於單個矢量大小。
我們還沒有看過任何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進行泛型推導,基本模式都是你會看到的。 我不會在這里詳細描述正在發生的事情,但請參閱我的博客文章,以便討論類似的例子。
現在我們可以這樣寫:
scala> (1, 2, 3, 4) |+| (5, 6, 7, 8)
res1: (Int, Int, Int, Int) = (6,8,10,12)
或這個:
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)
它只是有效。
我一直在使用元組來表示上面所有示例中的多維向量,但您也可以使用Shapeless的Sized
,它是一個同類集合,在類型級別對其長度進行編碼。 您可以為Sized
類型提供VectorLike
實例,而不是(或除了)元組實例,而不對VectorLike
本身進行任何更改。
您應該選擇哪種表示取決於許多因素。 元組很容易編寫,並且對於大多數Scala開發人員來說看起來很自然,但是如果你需要超過22維的向量,它們就無法工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.