How to decorate an immutable object graph from scala case classes
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 HasBazobject 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.
One other strategy might be to create yet another case class:
case class Foo( id: Int, bar_id: Int, baz_id: Int, x: Int, y: String)case class ProcessedFoo( foo: Foo, bar: Bar, baz: Baz)
Combining Option
and type parameters you can flag your case class, and track whether the processed fields are empty, statically:
import scala.language.higherKindsobject Acme { case class Foo[T[X] <: Option[X] forSome { type X }](a: Int, b: String, c: T[Boolean], d: T[Double]) // Necessary, Foo[None] won't compile type Unprocessed[_] = None.type // Just an alias type Processed[X] = Some[X]}
Example use case:
import Acme._val raw: Foo[Unprocessed] = Foo[Unprocessed](42, "b", None, None)def process(unprocessed: Foo[Unprocessed]): Foo[Processed] = unprocessed.copy[Processed](c = Some(true), d = Some(42d))val processed: Foo[Processed] = process(raw)// No need to pattern match, use directly the x from the Some case classprintln(processed.c.x)println(processed.d.x)
I used this once in my current project. The main problem I encountered is when I want Foo
to be covariant.
Alternatively, if you don't care about the bound on T
:
case class Foo[+T[_]](a: Int, b: String, c: T[Boolean], d: T[Double])
then you can use Foo[Unprocessed]
or Foo[Processed]
when you need a Foo[Option]
.
scala> val foo: Foo[Option] = processedfoo: Acme.Foo[Option] = Foo(42,b,Some(true),Some(42.0))