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!