Take the 2-minute tour ×

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?

share|improve this question

4 Answers 4

up vote 5 down vote accepted

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].

share|improve this answer

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/

share|improve this answer
Self types allow you to refer to this, whereas the CRTP pattern in Dario's answer does not. –  dsg Jan 23 '11 at 15:50
@dsg: What do you mean you can’t refer to this with CRTP? –  Debilski Jan 23 '11 at 23:15
Right, you can’t. –  Debilski Jan 24 '11 at 0:29

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]] {
share|improve this answer
Spot on. Syntax is correct and everything! I guess java does not support this (otherwise what's the deal with javax.vecmath)? –  dsg Jan 23 '11 at 12:13
Actually, I guess java does support this: stackoverflow.com/questions/2382915/… –  dsg Jan 23 '11 at 12:56

There is a big problem with having a common ancestor with CRTP pattern on JVM. When you execute the same abstract code with different implementations, JVM will de-optimize the code (no inlining + virtual calls). You will not notice this if you only test with Vec3D, but if you test with both Vec2D and Vec3D you will see a huge drop in performance. Moreover, Escape Analysis cannot be applied to the de-optimize code (no scalar replacement, no elimitation of new instances). The lack of these optimizations will slow your program by a factor of 3 (very rounded guess that depends on your code).

Try some benchmarks that run for about 10 seconds. In the same run test with Vec2D, then Vec3D, then Vec2D, then Vec3D again. You will see this pattern:

  • Vec2D ~10 seconds
  • Vec3D ~30 seconds
  • Vec2D ~30 seconds
  • Vec3D ~30 seconds
share|improve this answer

Your Answer


