1

In a small library I'll try to write, the end user-facing part looks like this:

case class Box(is: Item*)
case class Item(data: Int)

Box(Item(0), Item(1))

Ideally, however, each item should know in which box it is:

case class Item(data: Int, box: Box)

Is there any way to establish this bidirectional connection with immutable case classes? I tried to avoid something like this...

val box = Box()
val i = Item(0, box)
box.add(i) // requires var in Box :(

or this:

val i = Item(0, None) // requires Option :(
val box = Box(i)
i.box = Some(box) // requires var in Item :(

...by using lazy and implicits but couldn't come up with a solution. Is it possible at all?

From the following question, I learned that pass-by-name + lazy could be useful, but still, the box would be required to be passed explicitly: [Scala: circular references in immutable data types?. The goal is to make the end user-facing part as simple/slim as possible, behind the scenes as much magic as required could be used :)

1 Answers1

0

You could do something like this:

case class Box(is: (Box => Item)*) {
  lazy val items = is.map(_(this))
}
class Item(val data: Int, val box: Box)
object Item {
  def apply(data: Int)(box: Box): Item = new Item(data, box)
}

val b = new Box(Item(0), Item(1))

Downsides are the Box constructor is a bit weird, and Item can no longer be a case class since this relies on changing the Item object's apply method. But this gets you the exact same syntax for creating objects and does create circular references.

Joe K
  • 18,204
  • 2
  • 36
  • 58
  • 1
    One problem is `Box(Item(0)) == Box(Item(0)) == false`. Another one is that `Box(Item(0)).toString == Box(WrappedArray())`. Unapply doesn't work too. You'd have to do away with the case class and implement all the goodies explicitly to get this to work. – Dima Jul 27 '17 at 21:52
  • I agree, there are plenty of downsides to this approach. Seems like just using a `var` in `Item` would be not so bad, comparatively. but the original question asked specifically about how to do it immutably. – Joe K Jul 27 '17 at 22:47
  • Not using case class may be a good idea (case class with a var is asking for trouble anyhow). – Dima Jul 28 '17 at 00:22
  • Thanks for your recommendations. After all, it seems as there is no perfect solution. In my case (not stated in the question though), using companion objects is not an option as the lib's end user needs to subclass Item and the end user-facing part should more look like a DSL (very conservative enterprise environment, where they don't like custom code). And yes, case classes would be better (esp. for Item). So your solution is not applicable in my real-world situation, but as it answers my origianl question technically, I will accept it! – Nepomuk Hirsch Jul 28 '17 at 11:32