Scala - Adding Operators to LWJGL Classes (Vector/Matrix)

Started by kloffy, February 06, 2013, 01:19:27

Previous topic - Next topic

kloffy

I am not sure how big the audience is for using LWJGL in Scala. A quick search did not turn up too many topics. However, I am really liking some of the language features, so I figured I'll give it a shot. Anyway, if there are others that went down this path before, they surely considered doing something like this:

Since Scala allows operator overloading, it is would only be natural to provide overloads for the Vector/Matrix classes. Luckily, Scala even provides a mechanism to add on methods to existing types (referred to as the "Pimp My Library" pattern). In fact, the latest version of Scala has "impicit value classes", which are meant to simplify this pattern and minimize overhead. All of this works very well. However, things started to get slightly more complicated when I tried to reduce some code duplication between Vectors of different size. After some tinkering around, here is what I got:

package com.example

import org.lwjgl.util.vector._

trait GenericExtension[V,S] extends Any {
  type TVector = V
  type TScalar = S
  
  def map(f: (TScalar) => TScalar): TVector
  def map(f: (TScalar, TScalar) => TScalar)(other: TVector): TVector
}

trait VectorExtension[V] extends Any with GenericExtension[V, Float] {
  def copy(): VectorExtension[V]
  
  def +=(v: TVector) = map(_+_)(v)
  def -=(v: TVector) = map(_-_)(v)
  def *=(v: TVector) = map(_*_)(v)
  def /=(v: TVector) = map(_/_)(v)
  
  def +(v: TVector) = copy() += v
  def -(v: TVector) = copy() -= v
  def *(v: TVector) = copy() *= v
  def /(v: TVector) = copy() /= v
  
  def unary_+() = copy().map(+_)
  def unary_-() = copy().map(-_)
}

object LinearAlgebra {
  implicit class Vector2fExtension(val self: Vector2f) extends AnyVal with VectorExtension[Vector2f] {
    override def copy(): Vector2fExtension = new Vector2f(self)
    override def map(f: (TScalar) => TScalar): TVector = {
      self.set(
        f(self.x),
        f(self.y)
      ); self
    }
    override def map(f: (TScalar, TScalar) => TScalar)(other: TVector): TVector = {
      self.set(
        f(self.x, other.x),
        f(self.y, other.y)
      ); self
    }
  }
  implicit class Vector3fExtension(val self: Vector3f) extends AnyVal with VectorExtension[Vector3f] {
    override def copy(): Vector3fExtension = new Vector3f(self)
    override def map(f: (TScalar) => TScalar): TVector = {
      self.set(
        f(self.x),
        f(self.y),
        f(self.z)
      ); self
    }
    override def map(f: (TScalar, TScalar) => TScalar)(other: TVector): TVector = {
      self.set(
        f(self.x, other.x),
        f(self.y, other.y),
        f(self.z, other.z)
      ); self
    }
  }
  implicit class Vector4fExtension(val self: Vector4f) extends AnyVal with VectorExtension[Vector4f] {
    override def copy(): Vector4fExtension = new Vector4f(self)
    override def map(f: (TScalar) => TScalar): TVector = {
      self.set(
        f(self.x),
        f(self.y),
        f(self.z),
        f(self.w)
      ); self
    }
    override def map(f: (TScalar, TScalar) => TScalar)(other: TVector): TVector = {
      self.set(
        f(self.x, other.x),
        f(self.y, other.y),
        f(self.z, other.z),
        f(self.w, other.w)
      ); self
    }
  }
}


So, my question to those experienced Scala programmers out there is: Is this a reasonable approach? Maybe it would be better to just bite the bullet and duplicate the code for the sake of better performance. Or maybe there is a simpler/cleaner solution?

quew8

I'm not a scala programmer so I can't help you on that, but surely you are only ever going to be using one type of vector and one type of matrix? There's no point coding for something you're not going to use. It seems your saying code duplication or loss of performance when you can have both and make things simpler in the process.
Apologies if I'm completely wrong on this front or if you're only asking as a hypothetical situation (I think good programmers should always be doing this).

kloffy

Yes, that is a fair point. Trying to be overly general and accommodating cases that you'll never need is a danger to productivity.

However, in this case, I was mainly referring to the duplication between Vector2f, Vector3f and Vector4f. There are valid uses for each of these types - and there are equivalent operations on all of them (namely "+,-,*,/" with scalar and vector arguments).