Search code examples
scalafunctional-programmingimmutabilityobject-graph

Is it in some scenarios impossible to create immutable object graphs?


I'm aware that immutability is not always the holy grail. However, since I'm learning Scala for quite a while now, I always try to find an immutable solution at first, especially when it comes to pure "data objects". I'm currently in the progress of finding a method for creating an immutable object graph for a given scenario, but I'm not sure if this is possible at all.

I just want to create the graph once, changes after the creation are not necessary.

Imagine the following scenario:

  • There is only one type: Person.
  • Person objects can have two types of references:
    • there is a unidirected 1-n relationship between a Person and potential children (also of type Person).
    • Furthermore, wifes have husbands and vice versa

The first problem is that the relationship between two spouses is cyclic. Because setting references results in new objects (due to immutability), eventually spouse A is pointing to spouse B_old and spouse B is pointing to spouse A_old. Someone in another posting said that cyclic references and immutability are an oxymoron. I don't think this is always true since spouse A could create spouse B in its own constructor and pass this - but even when using this uncomfortable approach, adding the children references afterwards would change A and B again. The other way round - beginning with children and then linking spouses - results in a similar situation.

At the moment, I think there is no way to do this. but maybe I'm wrong and there are some patterns or workarounds I'm not aware of. If not, is mutability the only solution?


Solution

  • I can imagine several tricks how you can create immutable cycle, including, but not limited to:

    • privately mutable class, which is effectively immutable from outside
    • reflection

    But the one I like most (and it is truly scala-way) is carefully mixed lazy evaluation and by-name parameters:

    object DeferredCycle extends App {
    
      class C(val name:String, _child: => C) {
        lazy val child = _child
        override def toString: String = name + "->" + child.name
      }
    
      val a:C = new C("A", b)
      val b:C = new C("B", a)
    
      println(a)
      println(b)
    }
    

    Prints:

    A->B
    B->A