1

I am trying to write a scala-wrapper for matrix operations, using the ejml library. Basically I just use SimpleMatrix. However, I want different classes for matrix and vector, for example to only be able to invert a matrix or to explicitly state that a function returns a vector, not a matrix. Currently, I am having trouble returning the concrete classes instead of the trait.

I started with a trait, MLMatrixLike:

trait MLMatrixLike {
  def data: SimpleMatrix
  protected def internalMult(implicit that: MLMatrixLike): SimpleMatrix = {
    data.mult(that.data)
  }
  def *(implicit that: MLMatrixLike): MLVector = MLVector(internalMult)
}

Both my matrix class and my vector class are extending the trait:

case class MLMatrix(data: SimpleMatrix) extends MLMatrixLike {

  def this(rawData: Array[Array[Double]]) = this(new SimpleMatrix(rawData))

  def apply(row: Int, col:Int): Double = data.get(row, col)

  def transpose(): MLMatrix = MLMatrix(data.transpose())

  def invert(): MLMatrix = MLMatrix(data.invert())

  def *(implicit that: MLMatrix): MLMatrix = MLMatrix(internalMult)

  def *(that: Double): MLMatrix = MLMatrix(data.scale(that))

  def -(that: MLMatrix): MLMatrix = MLMatrix(data.minus(that.data))
}

object MLMatrix {
  def apply(rawData: Array[Array[Double]]) = new MLMatrix(rawData)
}

case class MLVector(data: SimpleMatrix) extends MLMatrixLike {

  def this(rawData: Array[Double]) = {
    this(new SimpleMatrix(Array(rawData)).transpose())
  }

  def apply(index: Int): Double = data.get(index)

  def transpose(): MLVector = MLVector(data.transpose())

  def -(that: MLVector): MLVector = MLVector(data.minus(that.data))
}

object MLVector {
  def apply(rawData: Array[Double]) = new MLVector(rawData)
}

To my mind, this setup is not very good. I would like to define multiply (*) only once, since the SimpleMatrix-call is always the same and I can infer from the type of the parameter "that" whether the return type should be a matrix or a vector. Thus I would like to define a single function in MLMatrixLike along the lines of this (not working) function:

def *[T <: MLMatrixLike](that :T): T = {
  new T(data.mult(that.data))
}

Of course, this does not work, since there is no such constructor T, but currently I fail to see, how I can get something similar to work. Returning MLMatrixLike is not correct to my mind, since that way I cannot check during compilation if the correct type is returned.

A similar problem applies to transpose, and minus - here the return type is always the own class.

Thank you very much!

swebbo
  • 308
  • 1
  • 7

1 Answers1

1

I'm not sure what the benefits of wrapping SimpleMatrix in two other classes are. However, you could solve the duplication problem by making MLMatrixLike generic with its self-type, and defining an abstract constructor.

trait MLMatrixLike[Self <: MLMatrixLike[Self]] {
  this: Self =>
  def data: SimpleMatrix

  def createNew(data: SimpleMatrix): Self

  def *[T <: MLMatrixLike[T]](that: T): T = that.createNew(data.mult(that.data))

  def *(that: Double): Self = createNew(data.scale(that))

  def -(that: Self): Self = createNew(data.minus(that.data))

  def transpose: Self = createNew(data.transpose())
}

case class MLMatrix(data: SimpleMatrix) extends MLMatrixLike[MLMatrix] {
  this: MLMatrix =>

  def this(rawData: Array[Array[Double]]) = this(new SimpleMatrix(rawData))

  override def createNew(data: SimpleMatrix): MLMatrix = MLMatrix(data)

  def apply(row: Int, col: Int): Double = data.get(row, col)

  def invert(): MLMatrix = MLMatrix(data.invert())

}

object MLMatrix {
  def apply(rawData: Array[Array[Double]]) = new MLMatrix(rawData)
}

case class MLVector(data: SimpleMatrix) extends MLMatrixLike[MLVector] {
  this: MLVector =>

  def this(rawData: Array[Double]) = {
    this(new SimpleMatrix(Array(rawData)).transpose())
  }

  override def createNew(data: SimpleMatrix): MLVector = MLVector(data)

  def apply(index: Int): Double = data.get(index)

}

object MLVector {
  def apply(rawData: Array[Double]) = new MLVector(rawData)
}

By the way, note, that a column vector times a row vector is a matrix, so the signature of the multiplication should probably not return the type of that. However, based on static information (you need to know the dimensions of both arguments) you cannot tell whether a multiplication returns a vector or a matrix, so you may aswell just return a MLMatrixLike.

Kulu Limpa
  • 3,501
  • 1
  • 21
  • 31
  • Thank you very much for your answer! – swebbo Oct 03 '15 at 09:01
  • You're welcome. Interestingly, since case-classes come with a `copy` method, you wouldn't even need to define an abstract constructor in `MLMatrixLike`. Unfortunately you need some black magic to make `MLMatrixLike` aware of the `copy` method, hence it's more of a plaything. See [this answer](http://stackoverflow.com/a/12521080/4041697) – Kulu Limpa Oct 03 '15 at 09:51