Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decorate an immutable object graph from scala case classes

I'm reading structured JSON, using Play Frameworks' JSON Reads to build up an object graph with case classes.

An example:

case class Foo (
                       id: Int,
                       bar_id: Int,
                       baz_id: Int,
                       x: Int,
                       y: String
                       )
{
  var bar: Bar = null
  var baz: Baz = null
}

After building the Foo, I must come back later and decorate it by setting bar and baz. Those are defined in other JSON files and only known when all parsing is complete. But this means Foo can't be immutable.

What is the "right" way in Scala to make an immutable object, and then a decorated version of it, without repeating every field of Foo multiple times, over and over?

I know several ways that feel wrong:

  • make "bar: Option[Bar]" and "baz: Option[Baz]" case class parameters, and then I can use "copy" to make new versions of the Foo class with them set to something; but then I have to check them every single time they're accessed - inefficient, unsafe, not able to make a DecoratedFoo that just is guaranteed to have the right structure
  • make a second case class that is a copy-paste of all the structure in the first, but adding the two extra decorated parameters - but this means echoing the entire parameter list in the definition, and again when creating instances of it
  • Case class inheritance is apparently controversial, and in any case also appears to require me to repeat every single parameter anyway, in the subclass constructor?
  • Make a non-case superclass listing the common case class parameters. Then extend it in the case class. But this would seem to still require repeating every single parameter in the subclass constructor as well.
  • I see blogs with people talking about this problem and using reflection at runtime to populate the base attributes of their decorated copies - this avoids echo but now you have no type safety, specifying attribute names as strings, overhead, etc...

Surely Scala must have a way to let people compose more complicated immutable objects out of simpler ones without having to copy each and every part of them by hand?

like image 215
user2057354 Avatar asked Sep 29 '22 16:09

user2057354


1 Answers

You could introduce a new trait for the processed types, a class that extends that trait, and an implicit conversion:

case class Foo(bar: Int)

trait HasBaz {
    val baz: Int
}

class FooWithBaz(val foo: Foo, val baz: Int) extends HasBaz

object FooWithBaz {
    implicit def innerFoo(fwb: FooWithBaz): Foo = fwb.foo

    implicit class RichFoo(val foo: Foo) extends AnyVal {
        def withBaz(baz: Int) = new FooWithBaz(foo, baz)
    }
}

So then you can do:

import FooWithBaz._
Foo(1).withBaz(5)

And, although withBaz returns a FooWithBaz, we can treat the return value like a Foo when necessary, because of the implicit conversion.

like image 75
Ben Reich Avatar answered Oct 06 '22 19:10

Ben Reich