I'm building an entire application out of immutable objects so that multi-threading and undo become easier to implement. I'm using the Google Collections Library which provides immutable versions of Map, List, and Set.
我正在用不可变对象构建一个完整的应用程序,以便多线程和撤消变得更容易实现。我正在使用谷歌集合库,它提供了映射、列表和集合的不变版本。
My application model looks like a tree:
我的应用模型看起来像一棵树:
- Scene is a top-level object that contains a reference to a root Node.
- Scene是一个*对象,它包含对根节点的引用。
- Each Node can contain child Nodes and Ports.
- 每个节点都可以包含子节点和端口。
An object graph might look like this:
对象图可能是这样的:
Scene
|
+-- Node
|
+-- Node
|
+- Port
+-- Node
|
+- Port
+- Port
If all of these objects are immutable, controlled by a top-level SceneController object:
如果所有这些对象都是不可变的,由*的SceneController对象控制:
- What is the best way to construct this hierarchy?
- 构建这种层次结构的最佳方式是什么?
- How would I replace an object that is arbitrarily deep in the object tree?
- 如何替换对象树中任意深度的对象?
- Is there a way to support back-links, e.g. a Node having a "parent" attribute?
- 是否有支持反向链接的方法,例如具有“父”属性的节点?
And more generally:
和更普遍的:
- Have any patterns emerged for dealing with this type of data?
- 处理这种类型的数据是否出现了任何模式?
- Is there (academic) literature available on the subject?
- 有关于这一主题的(学术)文献吗?
- Is this a good idea?
- 这是个好主意吗?
2 个解决方案
#1
11
There are two concepts of interest here. First, persistent data structures. If all elements of the tree are immutable, then one can derive a new tree from the original tree by replacing some parts, but referring to the older parts, thus saving time and memory.
这里有两个感兴趣的概念。首先,持久数据结构。如果树的所有元素都是不可变的,那么可以通过替换一些部分来从原始树中派生新的树,但是要引用旧的部分,从而节省时间和内存。
For example, if you were to add a third Port to the Node that has two ports already, you'd have to create a new Scene, a new Scene's Node's descendant, and the Node that you are changing. The other Node and all of the Ports do not need to be created anew -- you just refer to them in the new Scene/Nodes.
例如,如果要向已经有两个端口的节点添加第三个端口,就必须创建一个新场景、新场景的节点的后代以及要更改的节点。其他节点和所有端口不需要重新创建——您只需在新场景/节点中引用它们。
The other concept is that of a Zipper. A zipper is a way to "navigate" through a persistent data structure to optimize local changes. For instance, if you added four new Ports instead of just one, but you added each Port one at a time, you'd have to create four new Scenes, and eight new Nodes. With a zipper, you defer such creations until you are done, saving up on those intermediary objects.
另一个概念是拉链。zippers是一种通过持久数据结构“导航”以优化本地更改的方法。例如,如果您添加了四个新端口而不是一个,但是每次都添加了一个端口,那么您必须创建四个新场景和八个新节点。使用zippers,您可以将此类创建延迟到完成时,从而节省中间对象的开销。
The best explanation I ever read about zipper is here.
我读过的关于拉链的最好的解释在这里。
Now, use of a zipper to navigate a data structure remove the need to have back-links. You can have back-links in an immutable structure, by clever use of recursive constructors. However, such a data structure would not be persistent. Non-persistent immutable data structures have lousy modification performance, because you need to copy the whole data each time.
现在,使用zippers导航数据结构就无需使用反向链接。通过巧妙地使用递归构造函数,您可以在不可变结构中拥有反向链接。但是,这样的数据结构不会持久。非持久性不可变数据结构的修改性能很差,因为每次都需要复制整个数据。
As for academic literature, I recommend Purely Function Data Structures, by Okasaki (dissertation PDF, fully fledged book).
至于学术文献,我推荐纯粹的功能数据结构,由冈崎(论文PDF,完全成熟的书)。
#2
3
If your tree is immutable, then if you want to change it in anyway you have to produce a new tree.
如果你的树是不可变的,那么如果你想要改变它,你必须生成一个新的树。
This sounds bad, but its not if all your nodes are also immutable! Since you don't need to make copies of immutable objects, your new tree will mostly refer to the old tree except for the changes you made.
这听起来很糟糕,但如果所有的节点都是不可变的,那就不是这样了。由于您不需要复制不可变对象,所以除了所做的更改外,新树将主要引用旧树。
You'll have to design your tree in such a way that each immutable tree refers to other immutable trees. This way you won't need to reproduce the entire immutable tree either.
您必须设计您的树,使每个不变树引用其他不变树。这样,您也不需要重新生成整个不可变树。
But if you go the immutable tree route, then you can't have back links. Otherwise you can't reuse sub trees.
但是如果你走不可变的树路径,你就不能有反向链接。否则就不能重用子树。
#1
11
There are two concepts of interest here. First, persistent data structures. If all elements of the tree are immutable, then one can derive a new tree from the original tree by replacing some parts, but referring to the older parts, thus saving time and memory.
这里有两个感兴趣的概念。首先,持久数据结构。如果树的所有元素都是不可变的,那么可以通过替换一些部分来从原始树中派生新的树,但是要引用旧的部分,从而节省时间和内存。
For example, if you were to add a third Port to the Node that has two ports already, you'd have to create a new Scene, a new Scene's Node's descendant, and the Node that you are changing. The other Node and all of the Ports do not need to be created anew -- you just refer to them in the new Scene/Nodes.
例如,如果要向已经有两个端口的节点添加第三个端口,就必须创建一个新场景、新场景的节点的后代以及要更改的节点。其他节点和所有端口不需要重新创建——您只需在新场景/节点中引用它们。
The other concept is that of a Zipper. A zipper is a way to "navigate" through a persistent data structure to optimize local changes. For instance, if you added four new Ports instead of just one, but you added each Port one at a time, you'd have to create four new Scenes, and eight new Nodes. With a zipper, you defer such creations until you are done, saving up on those intermediary objects.
另一个概念是拉链。zippers是一种通过持久数据结构“导航”以优化本地更改的方法。例如,如果您添加了四个新端口而不是一个,但是每次都添加了一个端口,那么您必须创建四个新场景和八个新节点。使用zippers,您可以将此类创建延迟到完成时,从而节省中间对象的开销。
The best explanation I ever read about zipper is here.
我读过的关于拉链的最好的解释在这里。
Now, use of a zipper to navigate a data structure remove the need to have back-links. You can have back-links in an immutable structure, by clever use of recursive constructors. However, such a data structure would not be persistent. Non-persistent immutable data structures have lousy modification performance, because you need to copy the whole data each time.
现在,使用zippers导航数据结构就无需使用反向链接。通过巧妙地使用递归构造函数,您可以在不可变结构中拥有反向链接。但是,这样的数据结构不会持久。非持久性不可变数据结构的修改性能很差,因为每次都需要复制整个数据。
As for academic literature, I recommend Purely Function Data Structures, by Okasaki (dissertation PDF, fully fledged book).
至于学术文献,我推荐纯粹的功能数据结构,由冈崎(论文PDF,完全成熟的书)。
#2
3
If your tree is immutable, then if you want to change it in anyway you have to produce a new tree.
如果你的树是不可变的,那么如果你想要改变它,你必须生成一个新的树。
This sounds bad, but its not if all your nodes are also immutable! Since you don't need to make copies of immutable objects, your new tree will mostly refer to the old tree except for the changes you made.
这听起来很糟糕,但如果所有的节点都是不可变的,那就不是这样了。由于您不需要复制不可变对象,所以除了所做的更改外,新树将主要引用旧树。
You'll have to design your tree in such a way that each immutable tree refers to other immutable trees. This way you won't need to reproduce the entire immutable tree either.
您必须设计您的树,使每个不变树引用其他不变树。这样,您也不需要重新生成整个不可变树。
But if you go the immutable tree route, then you can't have back links. Otherwise you can't reuse sub trees.
但是如果你走不可变的树路径,你就不能有反向链接。否则就不能重用子树。