0
votes

I'd like a container class that I can extend with some number of traits to contain groups of default vals that can later be changed in an immutable way. The traits will hold certain simple pieces of data that go together so that creating the class with a couple of traits will create an object with several collections of default values.

Then I'd like to be able to modify any of the vals immutably by copying the object while changing one new value at a time.

The class might have something like the following:

class Defaults(val string: String = "string", val int: Int = "int")

Then other traits like this

trait MoreDefaults{
  val long: Long = 1l
}

Then I'd like to mix them when instantiated to build my the particular needed set of defaults

var d = new Defaults with MoreDefaults

and later to something like:

if (someFlag) d = d.copy( long = 1412341234l )

You can do something like this with a single case class but I run out of params at 22. But I'll have a bunch of groupings of defaults I'd like to mixin depending on the need, then allow changes to any of them (class defined or trait defined) in an immutable way.

I can stick a copy method in the Defaults class like this:

def copy(
    string: String = string,
    int: Int = int): Defaults = {
  new Defaults(string, int)
} 

then do something like

var d = new Defaults
if (someFlag) d = d.copy(int = 234234)

Question ====> This works for values in the base class but I can't figure how to extend this to the mixin traits. Ideally the d.copy would work on all vals defined by all of the class + traits. Overloading is trouble too since the vals are mainly Strings but all of the val names will be unique in any mix of class and traits or it is an error.

Using only classes I can get some of this functionality by having a base Defaults class then extending it with another class that has it's own non-overloaded copyMoreDefault function. This is really ugly and I hope a Scala expert will see it and have a good laugh before setting me straight--it does work though.

class Defaults(
    val string: String = "one",
    val boolean: Boolean = true,
    val int: Int = 1,
    val double: Double = 1.0d,
    val long: Long = 1l) {

  def copy(
      string: String = string,
      boolean: Boolean = boolean,
      int: Int = int,
      double: Double = double,
      long: Long = long): Defaults = {
    new Defaults(string, boolean, int, double, long)
  }   
}

class MoreDefaults(
    string: String = "one",
    boolean: Boolean = true,
    int: Int = 1,
    double: Double = 1.0d,
    long: Long = 1l,
    val string2: String = "string2") extends Defaults (
        string,
        boolean,
        int,
        double,
        long) {

  def copyMoreDefaults(
      string: String = string,
      boolean: Boolean = boolean,
      int: Int = int,
      double: Double = double,
      long: Long = long,
      string2: String = string2): MoreDefaults = {

    new MoreDefaults(string, boolean, int, double, long, string2)
  }

}

Then the following works:

var d = new MoreDefualts
if (someFlag) d = d.copyMoreDefaults(string2 = "new string2")

This method will be a mess if Defaults get's changed parameters! All the derived classes will have to be updated--ugh. There must be a better way.

1

1 Answers

1
votes

I don't think I'm strictly speaking answering your question, rather suggesting an alternative solution. So your having problems with large case classes, e.g.

case class Fred(a: Int = 1, b: Int = 2, ... too many params ... )

What I would do is organize the params into more case classes:

case class Bar(a: Int = 1, b: Int = 2)
case class Foo(c: Int = 99, d: Int = 200)
// etc
case class Fred(bar: Bar = Bar(), foo: Foo = Foo(), ... etc)

Then when you want to do a copy and change, say one of the values of Foo you do:

val myFred: Fred = Fred()
val fredCopy: Fred = myFred.copy(foo = myFred.foo.copy(d = 300))

and you need not even define the copy functions, you get them for free.