Search code examples
scalagenericsvectorscala-breeze

Generic Breeze Vector method


I am trying to implement a generic Scala method that processes Breeze vectors typed as Float or as Double (at least, less specificity a plus). Here is a simple example for Vector[Double]:

def vectorSum(vectors: Seq[Vector[Double]]): Vector[Double] = {
  vectors.reduce { (v1, v2) => v1 :+ v2 }
}

I am slightly new to Scala and Breeze, so my naive approach to make this generic is:

def vectorSumGeneric[T <: AnyVal](vectors: Seq[Vector[T]]): Vector[T] = {
  vectors.reduce { (v1, v2) => v1 :+ v2 }
}

However, this throws the following compile errors:

  • diverging implicit expansion for type breeze.linalg.operators.OpAdd.Impl2[breeze.linalg.Vector[T],breeze.linalg.Vector[T],That] starting with method v_v_Idempotent_OpAdd in trait VectorOps
  • not enough arguments for method :+: (implicit op: breeze.linalg.operators.OpAdd.Impl2[breeze.linalg.Vector[T],breeze.linalg.Vector[T],That])That. Unspecified value parameter op.

I've tried with some variations including T <% AnyVal and T <% Double, but they do not work either (as expected, probably). The Scala documentation to type bounds do not give me a clue about such a use case such as this. What is the correct way to solve this?


Solution

  • The problem is that the type parameter T can be anything, but you have to make sure that your type T supports at least addition as an algebraic operation. If T is a semiring, then you can add two elements of type T. You can enforce T to be a semiring by specifying a context bound:

    def vectorSum[T: Semiring](vectors: Seq[Vector[T]]): Vector[T] = {
      vectors.reduce(_ + _)
    }
    

    That way you enforce that for every instantiation of T you also have a Semiring[T] in your scope which defines the addition operation. Breeze already defines this structure for all primitive types which support addition.

    If you want to support more algebraic operations such as division, then you should constrain your type variable to have a Field context bound.

    def vectorDiv[T: Field](vectors: Seq[Vector[T]]): Vector[T] = {
      vectors.reduce(_ / _)
    }
    

    If you want to support general purpose element-wise binary operations on vectors:

    def vectorBinaryOp[T](
        vectors: Seq[Vector[T]], op: (T, T) => T)(
        implicit canZipMapValues: CanZipMapValues[Vector[T], T, T, Vector[T]])
      : Vector[T] = {
      vectors.reduce{
        (left, right) => implicitly[CanZipMapValues[Vector[T], T, T, Vector[T]]].map(left, right, op)
      }
    }
    

    Then you can define arbitrary binary operations on vectors:

    val vectors = Seq(DenseVector(1.0,2.0,3.0,4.0), DenseVector(2.0,3.0,4.0,5.0))
    val result = VectorSum.vectorBinaryOp(vectors, (a: Double, b: Double) => (a / b))