Вектор 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 измерениями, они не будут работать.