Clojure中有一个状态,它包含了多个子状态。

15

我很想听听这里的Clojure专家在管理层级状态方面有什么建议。 我发现自己经常使用{:structures {:like {:this {:with {:many 'levels}} } } },如果我希望在多个级别跟踪状态更改,则通过将原子值传递给它们 (atom {:like (atom 'this)}) ,我会觉得这样做可能是错误的。 通常情况下,是在顶层只使用一个原子,并且在映射中没有原子作为值更好吗?

3个回答

14

尽量避免在数据结构中使用嵌套的原子(atom)。

主要原因是不可变性会对你有所帮助。Clojure是一种以不可变数据结构为基础的函数式语言。大多数库都默认使用不可变数据结构,而Clojure的STM假定使用不可变数据结构可以获得最佳的并发性能。不可变性使你有机会在任何一个瞬间对整个状态进行一致性快照。基于不可变数据操作的纯函数易于开发和测试。

如果在数据结构中放置原子,则会失去所有不可变性优势,并且可能使代码非常复杂。如果数据结构包含许多可变组件,则很难推理其意义。

一些替代方法建议如下:

  • 将整个数据结构放入单个ref或atom中。这可以是一个巨大的数据结构,没有任何问题 - 我曾经编写过一个游戏,其中整个游戏地图都被保存在单个原子中,而没有遇到任何困难。
  • 使用各种旨在访问和更改嵌套不可变数据结构的方法:assoc-inget-inupdate-in等。
  • 使用递归函数使数据结构的导航更加可管理。如果一个节点具有相同“类型”的子节点,则通常暗示您应该使用某种形式的递归函数。

1
我喜欢将我的结构分解成较小的树的主要原因是,通常我想使用(add-watch)来监视多个小型原子,其中一个巨大原子的状态更改会导致状态更改通知对于监听器来说不明确。 - Hendekagon

12

您可以使用assoc-inget-inupdate-indissoc-in函数来处理嵌套结构。

这些函数非常方便,但我不知道它们是否能直接处理原子等。在最坏的情况下,您应该能够将它们嵌套到deref中,例如:

(def m (atom {:like {:this {:nested (atom {:value 5})}}}))

@(get-in @m [:like :this :nested])
; => {:value 5}

(get-in @(get-in @m [:like :this :nested]) [:value])
; => 5
你可以使用->使其更易读:
(-> @m
    (get-in [:like :this :nested])
    deref
    (get-in [:value]))
; => 5

关于嵌套的原子/引用/代理等问题,我认为这取决于您想要实现的目标。如果顶部只有一个,而且更改是同步的,那么事情会更容易理解。

另一方面,如果您不需要这种同步,那么做这件事将浪费时间,使用嵌套的原子/引用/代理会更好。

总之,我认为两种方式都没有“正确的方法”,它们都有其用途。


9

我更喜欢在顶层使用一个原子,因为这会使事情变得非常简单,也表明数据表示的是一种状态,该状态通过操作一次性修改。如果在每个级别上都放置原子,则会变得过于复杂,无法弄清楚正在发生什么。此外,如果在您的情况下嵌套过深,则建议您坐下来仔细考虑是否需要这样的结构,或者是否有更好的替代可能,因为这肯定会导致复杂性,直到嵌套数据是递归的(即每个级别上具有相同的结构)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接