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

时间:2011-04-15 20:55:35

标签: scala immutability cyclic-reference cyclic-graph

我搜索并找到了有关此主题的一些信息,但答案要么令人困惑,要么不适用。

我有这样的事情:

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

现在,我想说,加载一个文件,解析它并从中填充这个数据结构。它是不可变的和循环的,怎么会这样做?

另外,假设我确实填充了这个数据结构,现在我想修改它,比如更改rootThing.refs(3).name,怎么可能这样做?


感谢此处发布的想法。在这一点上,我在想如果一个人真的想要这样的东西的持久性数据结构,那就跳出框框思考并考虑客户端代码需要问什么问题。因此,不要考虑对象和字段,而应考虑查询,索引等。首先,我想的是: Is there a bidirectional multimap persistent data structure?

4 个答案:

答案 0 :(得分:13)

如果你准备修改它以引入一定程度的懒惰,你可以初始化这个表格的循环数据结构,

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

但是,你不能使它持久化:循环,任何变化都可以通过结构的每个元素看到,因此没有机会在版本之间共享。

答案 1 :(得分:5)

对于单个循环引用,您可以使用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)中,并在来自参考时查找它们。

修改

迈尔斯关于lazy val t的第一个案例是正确的。事实上,你需要按照他的答案中的名字参数。

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

答案 2 :(得分:5)

还有一种替代方法需要重新思考如何表示对象关联:而不是在对象内部的对象之间存储关联(通常在OO代码中完成),然后将它们作为地图添加到单独的层中。

由于创建时间关联时对象图中的所有节点都存在,因此可以使用地图轻松创建不可变的bidrectional关联。

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)

如果你考虑一下,这种方法与关系数据库的工作方式类似。表之间的多值关联不存储在行本身中,而是存储在单独的索引中。即使关联索引不存在,因此使用表扫描解析查询,它也会使用主键索引来查找结果的所有候选行。

这种风格的一个优点是可以在不影响对象本身的情况下添加或更改关联,并且可以针对不同的受众/用例使用不同的关联。缺点是在关联开始的实例上不能直接访问关联映射;它必须通过&单独提供。

答案 3 :(得分:3)

不可变数据结构可以完全由其构造函数初始化,或者您可以接受在更改其属性时继续复制结构的需要。因此,要回答问题的第一部分,您可以通过定义接受数据中所有信息的构造函数将数据加载到不可变数据结构中,或者确保您了解循环子图:

我认为循环数据结构不一定完全是循环的。如果您想象单个实例/状态所持有的指针网络,您可以拥有一个包含父和子的子图,这些子图指向彼此,但没有其他循环结构。在这种情况下,复制实例1以懒惰地创建具有不同父节点的实例2(例如)将需要复制父节点和子节点,因为它们形成循环子图。但是除了父级之外,子级中包含的引用可以继续引用与第一个实例相同的不可变结构。

例如,我的班级House提到了一个门,一个窗户和一个屋顶。 A门有颜色,有一个toHouse参考房子,窗户有一个大小,屋顶有一个间距。所以我创建了一个绿色的门,一个大窗户和一个平屋顶的bobsHouse。实际上,由于所有这些都是不可变的,理论上只有一个大型窗口 - 所有具有大型Windows的房屋都具有相同的窗口。第二个例子,janesHouse,就像bobsHouse,但是有山墙屋顶。所以,如果我说janesHouse = bobsHouse.withRoof(gabled),那么我应该得到一个新的House实例,一个新的(也是绿色的)门,一个新的(山墙)屋顶,但是有相同的窗口。

因此,如果janesHouse被懒惰地评估,它只需要在引用门或屋顶时创建一个新的房子。如果请求janesHouse.Window,则根本不需要创建新的House - 只需要bobsHouse。

tl; dr:您可以拥有持久(懒惰)循环数据结构,但前提是您可以在其中找到非循环子图,即它不是链。