Mike
Mike

Reputation: 1011

How do I overload my case class constructor to allow two different types?

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

Answers (2)

puhlen
puhlen

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

Paweł Motyl
Paweł Motyl

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

Related Questions