Reputation: 1011
Right now I have a RealVector
class and ComplexVector
class. The logic for them is almost identical, so I'd like to combine them into a single Vector
class. RealVector
takes a List[Double]
whereas ComplexVector
takes a List[ComplexNumber]
where ComplexNumber
is a case class I created.
How do I make it so my case class Vector
accepts either of the two List
types? Note that while the code for most methods are identical, some of them may return a Double
or ComplexNumber
depending on the List
type. Is it even correct to be using a case class in this instance, or should I be using a normal class?
edit: my current code
trait VectorElement[A]
implicit object RealVectorElement extends VectorElement[Double]
implicit object ComplexVectorElement extends VectorElement[ComplexNumber]
case class MyVector[A: VectorElement](components: List[A]) {
def +(that:MyVector[A]):MyVector[A] = {
if (this.dimension != that.dimension) throw new Exception("Cannot add MyVectors of different dimensions.");
new MyVector((this.components zip that.components).map(c => c._1 + c._2));
}
def -(that:MyVector[A]):MyVector[A] = {
if (this.dimension != that.dimension) throw new Exception("Cannot subtract MyVectors of different dimensions.");
new MyVector((this.components zip that.components).map(c => c._1 - c._2)); // ERROR HERE: error: value - is not a member of type parameter A
}
...
}
Upvotes: 0
Views: 1135
Reputation: 8519
The most familar way would be to create a common super type of your two Vector classes
abstract class Vector[A](elements: List[A]){
//common code
}
case class RealVector(elements: List[Double]) extends Vector[Double](elements)
case class ComplexVector(elements: List[ComplexNumber]) extends Vector[ComplexNumber](elements)
If you just want a single type can use Generics with case classes so case class Vector[A](values: List[A])
works.
Now this will allow any kind of List, so lets narrow it down. We could use a common supertype from a trait if we were dealing with custom types, but Double
is built in and we can't modify it.
What we can do is use a typeclass, this is a flexible form of polymorhism possible in scala. We can Define our typeclass with a trait.
trait VectorElement[A]
If we just want to use it to mark the types we want we are done, but we could also put common functionality that we need into here as well.
If we modify our case class definition to
case class Vector[A: VectorElement](values: List[A])
We restrict our generic type to only those types with a VectorElement instance available. The above code is syntactic sugar for case class Vector[A](values: List[A])(implicit ev: VectorElement[A])
we can now create instances for the types we need
implicit object RealVectorElement extends VectorElement[Double]
implicit object ComplexVectorElement extends VectorElement[ComplexNumber]
now as long as those two implicit objects are in scope we can use those types with our Vector
class but no others.
Some unrelated suggestions:
Vector
is already a class from the standard library which is always automatically imported, this has the potential to cause problems
List
is probably not the best collection type for this as it needs to traverse the collection to access it's elements. Likely you want to choose something more generic, and you probably want good indexed access. If you use IndexedSeq
instead of List
as the collection type, you can ensure that you are working with a collection with good index-based random access like Array
or Vector
(the one from the standard library).
Upvotes: 2
Reputation: 1332
You could try using some of the more advanced Scala type system features:
object Types {
trait inv[-A] {}
type Or[A, B] = {
type check[X] = (inv[A] with inv[B]) <:< inv[X]
}
}
case class Vector[U : (Double Or Int)#check](list: List[U]) {
def first(): U = list.head
}
I've used Double
and Int
here but any type can be used. Usage is simple:
println(Vector(List(1.0, 2.0, 3.0)).first()) // prints a Double
println(Vector(List(1, 2, 3)).first()) // prints an Int
//Vector(List("String")).first() // won't compile
Upvotes: 1