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 ?