правильная иерархия классов для 2D и 3D векторов - PullRequest
5 голосов
/ 23 января 2011

Я хочу иметь общий векторный абстрактный класс / черту, который определяет определенные методы, например:

trait Vec 
{
  def +(v:Vec):Vec
  def *(d:Double):Vec

  def dot(v:Vec):Double
  def norm:Double
}

Я хочу, чтобы Vec2D и Vec3D extension Vec:

class Vec2D extends Vec { /* implementation */ }
class Vec3D extends Vec { /* implementation */ }

Но как, например, сделать так, чтобы Vec2D можно было добавлять только к другим Vec2D, а не к Vec3D?

Прямо сейчас я просто реализую Vec2D и Vec3D без общего Vec предка, но это становится утомительно с дублированным кодом.Я должен реализовать все свои классы геометрии, которые зависят от этих классов (например, Triangle, Polygon, Mesh, ...) дважды, один раз для Vec2D и снова для Vec3D.

Я вижу реализации Java: javax.vecmath.Vector2d и javax.vecmath.Vector3d не имеют общего предка.В чем причина этого?Есть ли способ преодолеть это в скале?

Ответы [ 4 ]

7 голосов
/ 23 января 2011

Вы можете использовать собственные типы:

trait Vec[T] { self:T =>
  def +(v:T):T
  def *(d:Double):T

  def dot(v:T):Double
  def norm:Double
}

class Vec2D extends Vec[Vec2D] { /* implementation */ }
class Vec3D extends Vec[Vec3D] { /* implementation */ }

Но если обе реализации очень похожи, вы также можете попытаться абстрагироваться над измерением.

sealed trait Dimension
case object Dim2D extends Dimension
case object Dim3D extends Dimension

sealed abstract class Vec[D <: Dimension](val data: Array[Double]) { 

  def +(v:Vec[D]):Vec[D] = ...
  def *(d:Double):Vec[D] = ...

  def dot(v:Vec[D]):Double = ...
  def norm:Double = math.sqrt(data.map(x => x*x).sum)
}

class Vec2D(x:Double, y:Double) extends Vec[Dim2D.type](Array(x,y))
class Vec3D(x:Double, y:Double, z:Double) extends Vec[Dim3D.type](Array(x,y,z))

Конечно, это зависит от того, как вы хотите представлять данные и хотите ли вы иметь изменяемые или неизменные экземпляры.А для приложений "реального мира" вы должны рассмотреть http://code.google.com/p/simplex3d/

5 голосов
/ 24 января 2011

Когда запрошено , наиболее полезный способ разработки базовой черты включает как CRTP , так и аннотацию собственного типа .

trait Vec[T <: Vec[T]] { this: T =>
  def -(v: T): T
  def *(d: Double): T

  def dot(v: T): Double
  def norm: Double = math.sqrt(this dot this)
  def dist(v: T) = (this - v).norm
}

Без самодиагностики невозможно вызвать this.dot(this), поскольку dot ожидает T;поэтому нам нужно применить его к аннотации.

С другой стороны, без CRTP мы не сможем вызвать norm на (this - v), так как - возвращает T, и поэтому нам нужночтобы убедиться, что наш тип T имеет этот метод, например, объявить, что T является Vec[T].

4 голосов
/ 23 января 2011

Я не уверен в правильном синтаксисе Scala, но вы можете реализовать CRTP , то есть определить фактический тип через универсальный параметр.

trait Vec[V <: Vec[V]] {
  def +(v:V):V
  ...
}

class Vec2D extends Vec[Vec2D] { }
class Vec3D extends Vec[Vec3D] { }

class Polygon[V <: Vec[V]] {
  ...
}
2 голосов
/ 24 января 2011

Существует большая проблема с общим предком с шаблоном CRTP в JVM.Когда вы выполняете один и тот же абстрактный код с разными реализациями, JVM де-оптимизирует код (без вставки + виртуальные вызовы).Вы не заметите этого, если будете тестировать только с Vec3D, но если вы будете тестировать и с Vec2D, и с Vec3D, вы увидите значительное падение производительности.Более того, Escape-анализ нельзя применять к де-оптимизированному коду (без скалярной замены, без исключения новых экземпляров).Отсутствие этих оптимизаций замедлит вашу программу в 3 раза (очень округленное предположение, которое зависит от вашего кода).

Попробуйте некоторые тесты, которые выполняются около 10 секунд.В том же тесте запуска с Vec2D, затем Vec3D, затем Vec2D, затем снова Vec3D.Вы увидите этот шаблон:

  • Vec2D ~ 10 секунд
  • Vec3D ~ 30 секунд
  • Vec2D ~ 30 секунд
  • Vec3D ~ 30 секунд
...