在 FP 中,没有可变状态,每个操作都会返回一个新的世界状态。鉴于:我有一个联系人列表和一个个人联系人。
我将 Dirk 添加到我的地址簿中。德克是我地址簿的 child 。我的地址簿是 Dirk 的父级。由于我无法同时设置两个引用,因此我陷入了困境。亲子关系应该定义一个无限循环,我可以永远地从 parent 到 child ,再到 parent 再到同一个 child 。
使用 JavaScript 语法:
var addresses = new AddressBook();
var dirk = new Contact(addresses, 'Dirk', ...);
在第二行,我在没有 Dirk 的情况下传递地址簿。 Dirk 有一个对地址簿的父引用,其中没有他。
我怀疑答案,但我想确定。我真的要改变状态以正确设置它还是有一些我忽略的技术?
最佳答案
如果您希望这种事情像在您的 JavaScript 示例中那样工作(因此您可以直接在实际子项中查找实际地址簿),您必须使地址簿可变。这甚至不是因为父子项的初始创建(可以管理,在某些功能语言中比在其他语言中更容易),而是因为如果您继续添加对地址簿的进一步引用,旧条目将仍然保留他们过时版本的地址簿。
在 Clojure 中,在这种情况下很容易使用 Atom 或 Ref 来保存整个地址簿,然后还在每个子项中放置一个指向地址簿的 Atom 或 Ref,但 Clojure 引用类型实际上只是设计用于保存不可变数据嵌套它们可能会导致问题。
更好的解决方案是为您的实体提供符号名称(关键字、数字、UUID 都可以)并将它们存储在 map 中的某个地方。使用单个原子可能如下所示:
(def state (atom {:contacts {:dirk ...}
:address-books {}}))
然后你可以将 Dirk 添加到一个新的地址簿(以哈希映射的形式创建它),如下所示:
(swap! state (fn [state-map]
(update-in state-map [:address-book :my-address-book]
(fn [abook]
(let [entries (get abook :entries [])]
(assoc abook :entries (conj entries :dirk)))))))
请注意,这会将 Dirk 以符号引用 (
:dirk
) 的形式添加到地址簿中,以便在 :contacts
键下的顶级状态映射中查找。如果您还希望 Dirk 联系人维护其所属的地址簿列表,请使用进一步的 update-in
向 Dirk 联系人添加适当的信息,可能会删除一些与 ->
的嵌套:(-> state-map
(update-in [...] ...)
(update-in [...] ...))