如何在Scala中初始化和“修改”循环持久数据结构?

| 我已经搜索并找到了有关此主题的一些信息,但答案令人困惑或不适用。 我有这样的事情:
class Thing (val name:String, val refs:IndexedSeq[Ref])
class Ref (val name:String, val thing:Thing)
现在,我想说的是,加载文件,解析文件并从中填充此数据结构。它是不可变且循环的,怎么可能呢? 另外,假设我确实填充了此数据结构,现在我想对其进行修改,例如更改rootThing.refs(3).name,那怎么做? 感谢您在此处发布的想法。在这一点上,我正在考虑,如果人们真的想要像这样的持久性数据结构,那就跳出思路,考虑一下客户端代码需要提出的问题。因此,与其考虑对象和字段,不如考虑查询,索引等。首先,我在考虑以下方面: 是否存在双向多图持久数据结构?     
已邀请:
对于单个循环引用,可以使用lazy:
lazy val t: Thing = new Thing(\"thing\", Vector(new Ref(\"ref\", t)))
但是,显然,对于多对多连接而言,这变得很复杂。 我不知道是否存在通用的纯功能循环图数据结构。使用无环图,这很容易,因为您可以对其进行拓扑排序,然后逐步对其进行初始化。 也许使用间接寻址是您的一种选择,比如说通过标识符而不是实际的scala对象引用来引用对象?
case class ThingByID(id: Int, name: String, refs: IndexedSeq[RefByID])
case class RefByID(name: String, thingID: Int)
然后,您可以在加载文件后通过ID将事物收集到不可变的映射(例如
collection.immutable.IntMap
)中,并从引用中查找时进行查找。 编辑 Miles是关于ѭ4的第一种情况的。确实,您需要像他的回答中那样使用名字参数。
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)))
    
如果您准备对其进行修改以引入一定程度的惰性,则可以初始化这种形式的循环数据结构,
scala> class Thing (val name:String, refs0: => IndexedSeq[Ref]) { lazy val refs = refs0 } ; class Ref (val name:String, thing0: => Thing) { lazy val thing = thing0 }
defined class Thing
defined class Ref

scala> val names = Vector(\"foo\", \"bar\", \"baz\")                                                                                                                       
names: scala.collection.immutable.Vector[java.lang.String] = Vector(foo, bar, baz)

scala> val rootThing : Thing = new Thing(\"root\", names.map { new Ref(_, rootThing) })
rootThing: Thing = Thing@1f7dab1

scala> rootThing.refs(1).name
res0: String = bar
但是,您不能使其持久化:由于是周期性的,任何更改都可以通过结构的每个元素看到,因此没有机会在版本之间进行共享。     
还有一种替代方法,它需要重新考虑对象关联的表示方式:与其将对象之间的关联存储在对象自身内部(通常是用OO代码完成),然后将它们添加到一个单独的图层中作为Maps。 由于在创建对象关联时对象图中的所有节点都已存在,因此可以使用地图轻松创建不可变的双向关联。
scala> class Thing (val name:String)
defined class Thing

scala> class Ref (val name:String)
defined class Ref

scala> new Thing(\"Thing1\")
res0: Thing = Thing@5c2bae98

scala> new Ref(\"Ref1\")
res1: Ref = Ref@7656acfa       

scala> val thing2Ref = Map(res0 -> res1)
thing2Ref: scala.collection.immutable.Map[Thing,Ref] = Map(Thing@5c2bae98 -> Ref
@7656acfa)

scala> val ref2Thing = Map(res1 -> res0)
ref2Thing: scala.collection.immutable.Map[Ref,Thing] = Map(Ref@7656acfa -> Thing
@5c2bae98)
如果您考虑一下,则此方法类似于关系数据库的工作方式。表之间的多值关联不存储在行本身中,而是存储在单独的索引中。即使不存在关联索引,因此使用表扫描来解决查询时,它也使用主键索引来定位结果的所有候选行。 这种样式的优点是可以在不影响对象本身的情况下添加或更改关联,并且可以将不同的关联应用于不同的受众/用例。缺点是在关联开始的实例上不能直接访问关联图;它必须被传递并单独提供。     
不可变数据结构可以完全由其构造函数初始化,或者您可以接受在更改其属性时继续复制该结构的需要。因此,要回答问题的第一部分,您可以通过定义一个构造函数来将数据加载到不可变数据结构中,该构造函数接受数据中的所有信息,或者确保您知道循环子图: 我认为,循环数据结构不一定完全是循环的。如果您想象单个实例/状态包含的指针网络,则可以有一个子图,该子图包含相互指向的父级和子级,但没有其他循环结构。在这种情况下,复制实例1以使用不同的父节点(例如)延迟创建实例2,将需要复制父节点和子节点,因为它们形成了循环子图。但是,除了父级之外,子级中保留的子级引用可以继续是与第一个实例相同的不变结构的引用。 例如,我的房屋类中有对门,窗户和屋顶的引用。一扇门有一个颜色,而toHouse引用了该房屋,一个Window有一个尺寸,而Roof有一个间距。因此,我创建了带有绿色门,大窗户和平坦屋顶的bobsHouse。实际上,由于所有这些都是不可变的,因此从理论上讲只有一个大窗口-具有大窗口的所有房屋都具有相同的窗口。第二个实例janesHouse就像bobsHouse一样,但带有山墙屋顶。因此,如果我说janesHouse = bobsHouse.withRoof(gabled),那么我应该得到一个新的House实例,带有一个新的(也是绿色的)门和一个新的(山墙的)屋顶,但是具有相同的Window。 因此,如果janesHouse的评估是懒惰的,则仅当引用了Door或Roof时才需要创建一个新的House。如果请求janesHouse.Window,则根本不需要创建新的House-仅需要bobsHouse。 tl; dr:您可以拥有持久的(惰性)循环数据结构,但前提是您可以在其中找到非循环的子图,即它不是一个链。     

要回复问题请先登录注册