Clojure 中的指针循环

发布于 2025-01-05 12:14:21 字数 507 浏览 2 评论 0原文

我正在编写一个解析 XML 的 clojure 程序。作为其中的一部分,我希望基于 clojure.xml/parse 函数在 XML 文档中创建节点树。但是我希望树是双向的 - 也就是说,每个节点都有一个子节点列表和一个指向其父节点的指针。只有一个问题:所有数据都是不可变的,因此我无法在不更改子项的情况下“添加”指向父项的指针,从而使父项的指针毫无用处。

我找到了这个答案: 如何在 Clojure 中创建循环(且不可变)数据结构而不需要额外的间接?

建议的解决方案似乎是创建一个单独的索引映射,它引用内部的对象。对于一个更糟糕的解决方案来说,这似乎是一项巨大的工作量。我对树在构造过程中可变没有问题,但我不知道如何做到这一点。 clojure 中真的没有办法获取循环指针吗?

谢谢!

I'm writing a clojure program which parses XML. As a part of this, I wish to create a tree of the nodes in the XML document, based on the clojure.xml/parse function. However I'd like the tree to be bi-directional - that is, each node has a list of children, and a pointer to its parent. There is only one problem: all data is immutable, and so I can't 'add' a pointer to the parent without changing the child, thus making the parent's pointer useless.

I've found this answer: How can one create cyclic (and immutable) data structures in Clojure without extra indirection?

The solution suggested there seems to be creating a separate index map, which refers to the objects inside. This seems like a huge amount of work for a much worse solution. I'd have no problem for the tree to be mutable during construction, however I can't figure out how can it be done. Is there really no way to get a cyclic pointer in clojure?

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

赠意 2025-01-12 12:14:21

从逻辑上讲,不可能使纯不可变结构成为循环结构,因为通过添加父指针或子指针将会改变结构。

有一个可行的技巧,但我不确定我是否会推荐它:您可以将原子放入 Clojure 数据结构中,然后更改它们以建立必要的链接。例如,

(def parent {:id 1 :children (atom nil) :parent (atom nil)})

(def child  {:id 2 :children (atom nil) :parent (atom nil)}) 

(swap! (:children parent) conj child)
(reset! (:parent child) parent)

;; test it works
(:id @(:parent child))
=> 1

这在各个方面都是令人讨厌的:

  • 如果您尝试打印其中之一,它将导致您的 REPL 堆栈溢出,因为 REPL 不期望循环数据结构。
  • 它是可变的,因此您失去了不可变数据结构的所有可维护性和并发性优势(这是 Clojure 最好的事情之一!)
  • 如果您想复制节点,则需要获取节点的完整副本(例如构建一个新的 XML 文档),因为它不再是一个不可变的值。
  • 当您浏览结构时,取消引用所有原子可能会变得混乱。
  • 你会让那些习惯了惯用的 Clojure 的人感到困惑。

因此,如果您真的想这样做,这是可能的……尽管我个人认为,从长远来看,如果您使文档表示适当地不可变,您的情况仍然会好得多。如果您想导航结构,您也许可以在文档中使用更像 XPath 样式位置的内容。

It's logically impossible to make pure immutable structures cyclic, since by adding a parent or child pointer you would be mutating the structure.

There is a hack that works though I'm not sure I'd recommend it: you can put atoms inside Clojure data structures and then mutate these to make the necessary links. e.g.

(def parent {:id 1 :children (atom nil) :parent (atom nil)})

(def child  {:id 2 :children (atom nil) :parent (atom nil)}) 

(swap! (:children parent) conj child)
(reset! (:parent child) parent)

;; test it works
(:id @(:parent child))
=> 1

This is nasty in all sorts of ways:

  • It will cause your REPL to stack overflow if you try and print one of these because the REPL isn't expecting cyclic data structures.
  • It is mutable, so you lose all the maintainability and concurrency benefits of immutable data structures (which is one of the nicest things about Clojure!)
  • You'll need to take a full copy of a node if you want to duplicate it (e.g. building a new XML document), since it isn't an immutable value any more.
  • Dereferencing all the atoms can get messy as you navigate the structure.
  • You'll confuse people who are used to idiomatic Clojure.

So, it is possible if you really want to do it......... though I personally think you are still much better off in the long run if you make your document representation properly immutable. You could perhaps use something more like XPath style locations in the document if you want to navigate the structure.

缪败 2025-01-12 12:14:21

您是否考虑过使用拉链

拉链的设计目的是为了有效地处理树状结构。它包括基本操作,例如查看 子项 和在节点的 节点,同时还允许<一href="http://clojure.github.com/clojure/clojure.zip-api.html#clojure.zip/next" rel="nofollow">轻松迭代结构。

拉链是相当通用的,并且包含的​​功能已经允许从 XML 创建它们。 上的示例其他库页面提供了如何使用它们的良好初步图片。

对于 XML 拉链,您需要使用它

(clojure.zip/xml-zip (clojure.xml/parse file))

来创建初始结构。完成后,只需调用 root 即可获取最终结构。

Have you considered using zippers?

Zippers are designed for allowing for working with tree like structures effectively. It includes basic operations like looking at children and at the parent of a node while also allowing easily iterating through the structure.

Zippers are fairly generic and included functions already allow creating them from XML. An example on the Other Libraries page offers a good initial picture of how to work with them.

For an XML zipper, you'll want to use

(clojure.zip/xml-zip (clojure.xml/parse file))

to create the initial structure. When you're done, just call root to get the end structure.

公布 2025-01-12 12:14:21

像这样的事情是可行的:

(defprotocol TreeItemConstruction
  (add-child [this child]))

(deftype MyTree
  [parent ^:unsynchronized-mutable children]
  TreeItemConstruction
  (add-child [this child]
    (locking this
      (set! children (conj children child)))))

(defn my-tree
  [parent]
  (->MyTree parent []))

(def root (my-tree nil))
(def children (repeatedly 5 #(my-tree root)))
(doseq [child children] (add-child root child))

使协议不成为公共 API 的一部分,并且在构建后永远不要触及可变字段,这样你基本上就拥有了一棵不可变的树。然而,在构建后修改所述树将很困难。 YMMV。

Something like this could work:

(defprotocol TreeItemConstruction
  (add-child [this child]))

(deftype MyTree
  [parent ^:unsynchronized-mutable children]
  TreeItemConstruction
  (add-child [this child]
    (locking this
      (set! children (conj children child)))))

(defn my-tree
  [parent]
  (->MyTree parent []))

(def root (my-tree nil))
(def children (repeatedly 5 #(my-tree root)))
(doseq [child children] (add-child root child))

Make the protocol not part of the public API and never touch the mutable field after construction and you basically have an immutable tree. However modifying said tree after construction will be hard. YMMV.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文