I want to have a general vector abstract class / trait that specifies certain methods, e.g.:
trait Vec
{
def +(v:Vec):Vec
def *(d:Double):Vec
def dot(v:Vec):Double
def norm:Double
}
I want to have Vec2D
and Vec3D
extend Vec
:
class Vec2D extends Vec { /* implementation */ }
class Vec3D extends Vec { /* implementation */ }
But how can I, for instance, make it so that Vec2D
can only be added to other Vec2D
and not to Vec3D
?
Right now I'm just implementing Vec2D
and Vec3D
without a common Vec
ancestor, but this is getting tedious with duplicate code. I have to implement all my geometry classes that depend on these classes (e.g. Triangle
, Polygon
, Mesh
, ...) twice, once for Vec2D
and again for Vec3D
.
I see the java implementations: javax.vecmath.Vector2d
and javax.vecmath.Vector3d
do not have a common ancestor. What's the reason for this? Is there a way to overcome it in scala?
You can use self types:
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 */ }
But if both implementations are very similar, you could also try to abstract over the Dimension.
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))
Of course it depends on how you want to represent the data, and if you want to have mutable or immutable instances. And for "real world" applications you should consider http://code.google.com/p/simplex3d/
As requested, the most useful way of designing the base trait involves both the CRTP and the self-type annotation.
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
}
Without the self-type, it is not possible to call this.dot(this)
as dot
expects a T
; therefore we need to enforce it with the annotation.
On the other hand, without CRTP, we’ll fail to call norm
on (this - v)
as -
returns a T
and thus we need to make sure that our type T
has this method, e.g. declare that T
is a Vec[T]
.
I'm not sure about the proper Scala syntax, but you can implement the CRTP, i.e. define the actual type through a generic parameter.
trait Vec[V <: Vec[V]] {
def +(v:V):V
...
}
class Vec2D extends Vec[Vec2D] { }
class Vec3D extends Vec[Vec3D] { }
class Polygon[V <: Vec[V]] {
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With