Clojure states within states within states

前端 未结 3 1950
梦谈多话
梦谈多话 2021-02-13 02:45

I\'d love to hear what advice the Clojure gurus here have about managing state in hierarchies. I find I\'m often using {:structures {:like {:this {:with {:many \'levels}}

相关标签:
3条回答
  • 2021-02-13 03:19

    Don't use nested atoms in a data structure if at all possible.

    The main reason is that immutability is your friend. Clojure is a functional language that thrives on immutable data structures. Most libraries assume immutable data structures. Clojure's STM assumes immutable data structures to get the best possible concurrency. Immutability gives you the opportunity to take consistent snapshots of the entire state at any one instant. Pure functions that operate on immutable data are easy to develop and test.

    If you put atoms inside your data structures then you lose all the advantages of immutability and risk making your code very complex - it's a lot harder to reason about a data structure if it contains a lot of mutable components.

    Some suggested alternative approaches:

    • Put your entire data structure in a single ref or atom. This can be a huge data structure with no problem - I once wrote a game where the entire game map was held in a single atom without any difficulty.
    • Use the various methods that are designed for accessing and changing nested immutable data structures: assoc-in, get-in, update-in etc.
    • Use recursive functions to make navigating your data structure more managable. If one node of your structure has sub-nodes of the same "type" then it's usually a good hint that you should be using some form of recursive function.
    0 讨论(0)
  • 2021-02-13 03:20

    You can use assoc-in, get-in, update-in, and dissoc-in functions to work with nested structures.

    They are very convenient, but I don't know if they can handle atoms and such directly. In the worst case you should be able to nest them up to deref, e.g.:

    (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
    

    You can use -> to make this more readable:

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

    Regarding nested atoms/refs/agents, etc. I think it depends on what you're trying to achieve. It's certainly easier to reason about things, if there's just one of them at the top and the changes are synchronized.

    On the other hand, if you don't need this synchronization, you're wasting time in doing it, and you'll be better off with nested atoms/refs/agents.

    The bottom line is, I don't think either way is "the right way", they both have their usages.

    0 讨论(0)
  • 2021-02-13 03:30

    I would prefer to use one atom at top level as that would make things really simple and also that indicate that the data represent a state which is modified at once n all by an operation. If you put atoms at each level then it would become way too complex to figure out what is going on. Also if in your case the nesting is going way too deep then I would suggest you to sit back and think carefully whether you need such a structure or there can be any better alternate possible because this will certainly lead to complexity until the nested data is recursive (i.e same structure at each level)

    0 讨论(0)
提交回复
热议问题