Search code examples
scalagenericsabstractiongeneric-programmingtraits

Implement abstract behaviour just once... trait as contract, abstract class as concrete-helper


I'm currently thinking about refactoring my personal linear-algebra package. One thing that really bothers me is:

Currently I only support Vectors and Matrices that consist of floats. Now I'd like to add in support for ints, doubles and possibly even for booleans, but I'll have to think about that.

One thing tho: I do not want to write code multiple times. In fact, that's one thing I truly hate in programming. I want a single source of responsibility for a certain code/behaviour.

For most, well, all operations a Vector has there is a certain pattern. It doesn't matter if I add two Two-dimensional-vectors that hold ints or floats, the operation is always the same.

Now, naively I though "Well, how difficult can this be?!" Turns out, for me as a Scala-noobie, it's not that easy.

I began with:

trait Vector[Vec <: Vector]
{
  def add(v: Vec): Unit
  def subtract(v: Vec): Unit
  def multiply(v: Vec): Unit
  def multiply(s: Numeric): Unit
  def dot(v: Vec): Numeric
  def length(): Numeric
  def distance(v: Vec): Numeric
  def normalise(): Unit
  def divide(length: Numeric): Unit
  def toArray(): Array[Numeric]
}

My thoughts: Adding in a bound would help me, as I proceed.

Example:

abstract class Vector2(var x: Numeric, var y: Numeric) extends Vector[Vector2]
{
  override def add(v: Vector2): Unit =
  {
    this.x += v.x
    this.y += v.y
  }
//...
}

Then I wanted to create sub-types like:

class IntVector2(var x: Int, var y: Int) extends Vector2

and be done with it.

Problem begins here:

abstract class Vector2(var x: Numeric, var y: Numeric) extends Vector[Vector2]
{
  override def add(v: Vector2): Unit =
  {
    this.x += v.x // **here is the problem **
    this.y += v.y
  }
//...
}

It says

Type mismatch, expected: String, actual: Numeric

I thought I'd be brainy by using Numeric as the upper-bound, as I assumed an addition would be defined for all... how wrong was I?

How can I solve this? Any ideas?

Also, before I forget...

Assume cases where I need to use a helper, like math.sqrt(...). What to do there?

Currently (only implemented for floats, mind!) I do this:

def length(): Float =
{
  math.sqrt(x * x + y * y).toFloat
}

Now if I want to have the same for doubles and ints, how can I make this generic? Like, without .toFloat

Also, I'm fully aware that with booleans I would get a problem, as some operations are simply not defined at all... No reason to freak out, I hope :)


Solution

  • You should really consider looking inside scala standard collection library. Give attention to all classes that ends with Like: TraversableLike, SeqLike, SetLike and so on. They use higher-order types and typeclass polymorphism. Notice all uses of implicit arguments to methods like def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Repr, B, That]): That.

    Here is the project that provide many numeric abstractions and may be used as a reference.