Perhaps a Scala expert with a good sense of style and elegance can help me figure out a nicer way to structure the following code, which has a constructor "push-out" problem.
We start with a simple base class:
class Foo(val i: Int, val d: Double, val s: String) {
def add(f: Foo) = new Foo(i + f.i, d + f.d, s + f.s)
override def toString = "Foo(%d,%f,%s)".format(i,d,s)
}
For type-checking purposes in a complex application, I require a sub-class without any additional state:
class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {
override def toString = "Bar(%d,%f,%s)".format(i,d,s)
}
As it stands, when I add two Bars, I only get back a Foo:
val x = new Bar(1,2.3,"x")
val y = new Bar(4,5.6,"y")
val xy = x.add(y)
with the following response in the REPL:
x : Bar = Bar(1,2.300000,x)
y : Bar = Bar(4,5.600000,y)
xy : Foo = Foo(5,7.900000,xy)
How do I get two Bars to add together to form another Bar (rather than a Foo), in an elegant way, without having to copy and paste Foo's add method, as below?
class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {
// ugly copy-and-paste from Foo:
def add(b: Bar) = new Bar(i + b.i, d + b.d, s + b.s)
override def toString = "Bar(%d,%f,%s)".format(i,d,s)
}
I have many such Bars (all essentially copies of Foo, but very important for type checking), a cut-and-paste-free solution will pay dividends.
Thanks!
I try to avoid inheritance as much as possible. So here is an alternative approach.
Class Bar
has exactly the same constructor than Foo
and both are stateless. If you want to have several sub-types, just to convey any additional information, you can use a generic parameter as a "label". For instance:
trait Kind
trait Bar extends Kind
class Foo[T<:Kind](val i: Int, val d: Double, val s: String) {
def add(f: Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
override def toString = "Foo(%d,%f,%s)".format(i,d,s)
}
scala> val b1 = new Foo[Bar](2,3.0,"hello")
b1: Foo[Bar] = Foo(2,3.000000,hello)
scala> val b2 = new Foo[Bar](3,1.0," world")
b2: Foo[Bar] = Foo(3,1.000000, world)
scala> b1 add b2
res1: Foo[Bar] = Foo(5,4.000000,hello world)
Now add
is type safe. You could then use a type class to get the toString
to display the Kind
.
Expanding on @paradigmatic's answer, if you want to be able to support operations that are specific to each Bar
(e.g. different toString
), you can go one step further and make Kind
a typeclass.
trait Kind[T] { def name : String }
trait Bar
implicit object BarHasKind extends Kind[Bar] { val name = "Bar" }
class Foo[T : Kind](val i : Int, val d : Double, val s : String) {
def add(f : Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
override def toString = implicitly[Kind[T]].name + "(%d,%f,%s)".format(i,d,s)
}
scala> val b1 = new Foo[Bar](2, 3.0, "hello")
b1: Foo[Bar] = Bar(2,3.000000,hello)
trait Biz
implicit object BizHasKind extends Kind[Biz] { val name = "Biz" }
scala> val b2 = new Foo[Biz](1, 1.0, "One")
It is just as type safe as before:
scala> b1 add b2
<console>:16: error: type mismatch;
found : Foo[Biz]
required: Foo[Bar]
scala> b2 add b2
resN: Foo[Biz] = Biz(2,2.000000,OneOne)
For any property that you wish to be dependent on the tag, declare them abstractly in Kind
and provide the implementations in the implicit objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With