12

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

我有这样的事情:

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

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

另外,假设我确实填充了这个数据结构,现在我想修改它,比如更改 rootThing.refs(3).name,如何做到这一点?


感谢您在此处发布的想法。在这一点上,我在想,如果一个人真的想要这样的持久数据结构,那么跳出框框思考并考虑客户端代码需要提出哪些问题。因此,与其考虑对象和字段,不如考虑查询、索引等。首先,我在考虑: 是否有双向多图持久数据结构?

4

4 回答 4

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

但是,您不能使其持久化:由于是循环的,任何更改都可以通过结构的每个元素可见,因此没有机会在版本之间共享。

于 2011-04-15T22:16:42.277 回答
5

There's an alternative approach which requires rethinking how object associations are represented: instead of storing associations between objects inside the objects themselves (as typically done in OO code) add them afterwards in a separate layer as Maps.

Because all of the nodes in the object graph exist by the time associations are created, immutable bidrectional associations can be easily created using 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)

If you think about it, this approach is similar to how relational databases work. Multi-valued associations between tables are not stored in the rows themselves, but in separate indexes. Even when association indexes are not present and so a query is resolved using a table scan, it is using the primary key indexes to locate all candidate rows for the result.

An advantage of this style is that associations can be added or changed without affecting the objects themselves, and different associations can be employed for different audiences/use-cases. A disadvantage is that an association map is not directly accessible on instances where the associations begins; it has to be passed around & provided separately.

于 2011-10-06T13:04:13.200 回答
5

对于单个循环引用,您可以使用惰性:

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)中,并在来自 ref 时查找它们。

编辑

迈尔斯对第一个案例的看法是正确的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)))
于 2011-04-15T22:20:12.020 回答
3

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

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

例如,我的类 House 引用了 Door、Window 和 Roof。Door 有颜色,toHouse 引用了 House,Window 有 size,Roof 有 pitch。所以我创建了带有绿色门、大窗户和平屋顶的 bobsHouse。事实上,由于所有这些都是不可变的,理论上只有一个大窗户——所有有大窗户的房子都有相同的窗户。第二个实例 janesHouse 与 bobsHouse 类似,但带有山墙屋顶。因此,如果我说 janesHouse = bobsHouse.withRoof(gabled),那么我应该得到一个新的 House 实例,带有一个新的(也是绿色的)门和一个新的(山墙)屋顶,但具有相同的窗口。

因此,如果 janesHouse 被延迟评估,它只需要在引用了 Door 或 Roof 时创建一个新的 House。如果 janesHouse.Window 被请求,它根本不需要创建一个新的房子——只需要 bobsHouse。

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

于 2011-04-16T22:15:37.727 回答