Вектор Scala любой размерности c использованием бесформенного
Мне нужно измерить расстояние в n-мерном евклидовом пространстве, поэтому я должен создавать многомерные векторы и иметь возможность сравнивать их размеры и выполнять некоторые основные операции, такие как " + "или" -". Итак, я думал, что буду использовать классы типов + бесформенные, как показано здесь:
реализация общего вектора в Scala
но после того, как я потратил много времени на это, я все еще не могу понять, как это реализовать. Я имею в виду, я могу понять идею, которая стоит за классами типов и могут их использовать, но понятия не имеют, как применять к ним бесформенные. Заранее спасибо за любую помощь, как минимум самый простой пример, показывающий, как использовать классы типов с бесформенными.
1 ответов
первым шагом является поиск или определение класса типа с операциями, которые вы хотите поддерживать. scala.math.Numeric
одна возможность-это обеспечивает сложение, вычитание и т. д., но тот факт, что он также требует преобразования в и из, например,Int
означает, что это, вероятно, не правильный выбор здесь. Такие проекты, как алгебра и спиреи включить более подходящих кандидатов.
определение типа class
чтобы держать вещи просто мы можем просто определить наш собственный:
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
более распространены.)
определение экземпляры
Далее мы можем определить экземпляр класса типа для a двумерный вектор двойников, например:
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
часто удобно определить неявный класс "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
экземпляр работает, определение этих экземпляров для каждого числового типа раздражает и шаблонно-y. Мы можем улучшить ситуацию, предоставив экземпляры для любого двумерного вектора числовых типов с помощью 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)
это большое улучшение, но это все еще только для одного размера вектора.
получение экземпляров с бесформенными
мы еще не видел бесформенного, но теперь, когда мы хотим абстрагироваться от кортежа арити, это именно то, что нам нужно. Мы можем переписать наш общий экземпляр для работы с произвольными значениями числовых типов. Есть несколько способов написать это, но вот как я бы начал:
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)))
}
это выглядит сложным, и это так, но основной шаблон-это то, что вы увидите в любое время, когда вы используете бесформенный для общего вывода. Я не буду подробно описывать, что здесь происходит, но см., например мой пост в блоге здесь для обсуждения аналогичного примера.
теперь мы можем написать что-то вроде этого:
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
, который представляет собой однородную коллекцию, которая кодирует свою длину на уровне типа. Вы могли бы обеспечить VectorLike
экземпляров Sized
типы вместо (или в дополнение к) экземпляров кортежа, без внесения каких-либо изменений в VectorLike
сам.
какое представление вы должны выбрать, зависит от ряда факторов. Кортежи легко писать и будут выглядеть естественно для большинства разработчиков Scala, но если вам нужны векторы с более чем 22 измерениями, они не будут работать.