How to decorate an immutable object graph from scala case classes How to decorate an immutable object graph from scala case classes json json

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))