scalaimmutabilitycyclic-referencecyclic-graph

How to initialize and "modify" a cyclic persistent data structure in Scala?


I have searched and found some info on this topic but the answers are either confusing or not applicable.

I have something like this:

class Thing (val name:String, val refs:IndexedSeq[Ref])
class Ref (val name:String, val thing:Thing)

Now, I want to say, load in a file, parse it and populate this data structure from it. It being immutable and cyclic, how might one do so?

Also, let's say I do get this data structure populated, now I want to modify it, like change rootThing.refs(3).name, how might one do that?


Thanks for the ideas posted here. At this point, I'm thinking that if one really wants persistent data structures for something like this, to think outside the box and consider what questions client code will need to ask. So instead of thinking of objects and fields, think of queries, indexes and such. To start with, I'm thinking in terms of: Is there a bidirectional multimap persistent data structure?


Solution

  • For a single cyclic reference, you can use lazy:

    lazy val t: Thing = new Thing("thing", Vector(new Ref("ref", t)))
    

    However obviously this gets complicated with many-to-many connections.

    I don't know if a general purpose purely functional cyclic graph data structure exists. With acyclic graphs this would be easy as you could topologically sort it and then initialize it step by step.

    Maybe using an indirection is an option for you, say to refer to objects through an identifier instead of the actual scala object reference?

    case class ThingByID(id: Int, name: String, refs: IndexedSeq[RefByID])
    case class RefByID(name: String, thingID: Int)
    

    Then you could after loading your file collect the things by their ID into an immutable map (e.g. collection.immutable.IntMap) and look them up when coming from a ref.

    EDIT

    Miles is right about the first case of the lazy val t. Indeed you need by-name parameters as in his answer.

    class Thing(val name: String, val refs: IndexedSeq[Ref])
    class Ref(val name: String, _thing: => Thing) { def thing = _thing }
    
    val t: Thing = new Thing("thing", Vector(new Ref("ref", t)))