Clone objects with arguments override in Scala 2.8: best practices ?
I'm actively looking for best practices and patterns about how to create clone objects in Scala, with the possibility to change some values of the cloned object along the way.
I have the usual concern of the Java world on that topic: I would like to avoid Java "clone", I want to be able to use the pattern in a class hierarchy, I want to have the minimum amount of code (or at least, the simplest possible) to write in domain class to support my cloning API.
And as I'm in Scala, I want to deal with both vars and vals, if possible in the same consistent way.
Actually, I want to provide to my libraries consumer an effective way to do things like that (where *Child* extends *Parent* class):
val p = new Parent("some value")
// simple cloning
val p2 = p.copy
//clone, and change a val and a var along the way
val p3 = p.copy(p_val1 = "new value for val", p_var1 = "new value for var")
//and same things apply for children of Parent:
val c = new Child("some parent value", "some child value")
//override child val/var and/or parent val/var in the same fashion
val c1 = c.copy(p_val1 = "new value for val defined in Parent class",
c_val1 = "new value for val defined in Child class",
p_var1 = "new value for var defined in Parent class",
c_var1 = "new value for var defined in Child class" )
As Scala 2.8 is advertised with "default and named arguments", such things should possible (well, hopefully easy too ;). I came with this solution:
class A(val val_a:String) {
var var_a = ""
}
object A {
def merge[T](t:T)
(
var_a : String
) : T = {
t match {
case x:A => x.var_a = var_a
case _ => error("")
}
t
}
def copy(source:A)
(
val_a : String = source.val_a,
var_a : String = source.var_a
) : A = merge(new A(val_a))(var_a)
}
class B(override val val_a:String, val val_b : Int) extends A(val_a) {
var var_b = 0
}
object B {
def merge[T](t:T)
(
var_a : String,
var_b : Int
) : T = {
A.merge(t)(var_a) match {
case x:B => x.var_b = var_b
case _ => error("")
}
t
}
def copy(source:B)
(
val_a : String = source.val_a,
var_a : String = source.var_a,
val_b : Int = source.val_b,
var_b : Int = source.var_b
) : B = merge(new B(val_a,val_b))(var_a,var_b)
}
class C(override val val_a : String) extends A(val_a)
// *****************
// Example of use
// *****************
object TestCloning {
def main(args:Array[String]) {
val a1 = new A("la_init")
a1.var_a = "ra_init"
val b1 = new B("lb_init",1)
b1.var_a = "rb_init"
b1.var_b = 10
//example with A
val a2 = A.copy(a1)()
assert(!(a1 eq a2))
assert(a2.var_a == a1.var_a)
assert(a2.val_a == a1.val_a)
val a3 = A.copy(a1)(val_a = "la_mod")
assert(a3.var_a == a1.var_a)
assert(a3.val_a == "la_mod")
val a4 = A.copy(a1)(val_a = "la_mod", var_a = "ra_mod")
assert(a4.val_a == "la_mod")
assert(a4.var_a == "ra_mod")
val a5 = A.copy(new C("foo"))()
assert(a5.var_a == "")
assert(a5.val_a == "foo")
val a6 = A.copy(b1)()
assert(a6.val_a == "lb_init")
assert(a6.var_a == "rb_init")
//with B
val b2 = B.copy(b1)()
assert(b2.val_a == "lb_init")
assert(b2.var_a == "rb_init")
assert(b2.val_b == 1)
assert(b2.var_b == 10)
val b3 = B.copy(b1)(var_b = 5, val_a = "lb_mod")
assert(b3.val_a == "lb_mod")
assert(b3.var_a == "rb_init")
assert(b3.val_b == 1)
assert(b3.var_b == 5)
}
That solution seems to work, and allows to do the kind of use case I wanted to build.
But it seems not satisfactory at all for (at least) these reasons:
1/ - the trailing "()" in real clone without override is mandatory and doesn't look right;
2/ - it seems to be a lot of tedious code to write in each class, and so seems to be really error prone;
3/ - I'm not at ease with the merge method, but I can't say why exactly;
4/ - I don't like to much the factory approach.
1/ could be easily addressed if in place of only a copy(source:X)(.....) we define a couple of method :
def copyWith(source:A)( ... ) : A = merge(...)(...)
def copy(source:A) = copyWith(source)()
3/ and 4/ are just feelings, and so could be shut up for now.But 2/ is a real concern, and I would like to find a way to externalize more code in traits, so that domain classes have only simple, short code to write to provide a clone API.
So Scalazy, what are your best practices about the cloning topic in Scala ?
