# Generalizing Vector

I would like to generalize the Vector class to add some basic math features. I have done this for Vector[Scalar] (based on my Scalar class), a portion of which is shown below. How can I generalize this to also work for Vector[Int] and Vector[Double] without repeating the whole implementation? I am using Scala 3. Thanks.

implicit class Vectorx(self: Vector[Scalar]):
// adds basic math operations to the Vector[Scalar] class

import self._

def apply(i: Int): Scalar = self(i)
def unary_- = map(-_)

protected def zip(v: Vector[Scalar]) = { check(v); self zip v }

def + (v: Vector[Scalar]) = zip(v).map(t => t._1 + t._2)
def - (v: Vector[Scalar]) = zip(v).map(t => t._1 - t._2)

def * (s: Scalar) = map(_ * s)
def / (s: Scalar) = map(_ / s)
def % (s: Scalar) = map(_ % s)

// more

Using a typeclas.
Numeric from the stdlib could be used or serve as inspiration.

1 Like

Iâ€™d still like to know how to generalize this implicit class to handle any numeric type that has basic mathematical operators such as +, -, *, /, etc. When I try to generalize it as

implicit class Vectorx[A](self: Vector[A]):

the compiler tells me that type A does not support those operators. I looked for a trait, but there does not seem to be one. Is there a way to limit the type of A to a discrete set of types, such as Int, Long, Float, Double, etc.? I am using Scala 3. Thanks.

You could specify a subtype for each primitive numeric type, and maybe one for BigInt. There arenâ€™t that many, and maybe you donâ€™t need them all? Maybe Double and Long or BigInt is all you need?

I used to think, in the past, that there should be a trait Number, which provides basic math operators, and from which all primitive numbers inherit.

I have since changed my mind, since the practical use of such a super-type is very limited.

Although all the numbers have basic math, they behave differently. Division by zero throws an exception for integers, but returns a special value for floating points. And 3/2 is very different from 3.0/2.0.

Overflow is different. Integers wrap around, while floating points result in special values.

So, whatâ€™s left? Matrix multiplication can be written the same for integers and floating points, but for integers, you can run into overflows very quickly, so maybe you will need a BigInt anyway? For more serious linear algebra, like matrix inversion or finding eigenvalues, I donâ€™t think one can use the same algorithm for integers as for floating points.

So, you can try to generalize over all primitive numeric types, but it may be a lot of work for relatively little gain.

1 Like

Thanks for the reply. The generalized numeric Vector is not something I really need. I was really just curious about it and wondering if I was missing some simple little trick.

Iâ€™m not sure what you mean by â€śspecify a subtype for each primitive numeric type.â€ť Can you elaborate on that? You are right that I donâ€™t need very many. Iâ€™d settle for Int, Long, Bigint, Float, Double, and perhaps BigDecimal.

I meant something like:

trait Vectorx[A]
implicit class VectorxLong(self: Vector[Long]) extends Vectorx[Long]
etc

If you really want this, in spite of the drawbacks and limitations @curoli has listed, @BalmungSanâ€™s advice sounds just right. Based on Numeric (in Scala 2):

implicit class NumVector[T](v: Vector[T])(implicit num: Numeric[T]) {
def +(o: Vector[T]): Vector[T] = v.zip(o).map((num.plus _).tupled)
def -(o: Vector[T]): Vector[T] = v.zip(o).map((num.minus _).tupled)
def *(o: Vector[T]): Vector[T] = v.zip(o).map((num.times _).tupled)
def unary_- : Vector[T] = v.map(num.negate)
// ...
}
1 Like

I was hoping somebody would bring up Numeric so I wouldnâ€™t have to

A few caveats about Numeric:

• It wasnâ€™t created as a full-on effort to really fully support abstracting across numeric types. Itâ€™s more a minimal thing that was made to support collections methods such as .sum, .product, and .mean. So it has basic stuff but depending on what youâ€™re doing you may hit a wall.
• In high-performance contexts where youâ€™re crunching tons of data and every cycle and every byte counts, routing operations through the typeclass might be unacceptable performancewise. Of course in many applications it doesnâ€™t matter at all.
3 Likes

It is usually better to put the implicit argument in each method so the whole implicit class can be a value class.

Thankfully, the new syntax for extension methods in Scala 3 removes the need for such boilerplate.

1 Like

Also, I would like to point out that it doesnâ€™t support division and if you want that you have to choose between Integral and Fractional.

BTW,

routing operations through the typeclass might be unacceptable performancewise

Just out of curiosity, shouldnâ€™t the JIT just inline all the indirections?

Uh, maybe? Not sure if anybody has benchmarked that. I probably should have used some more cautious wording like â€śconsider a possible performance impact, dependingâ€ť. Would love to learn that I was too pessimistic on this point.

1 Like