I've decided to try and write a simulation program in Clojure (as a proof of concept) where:
- The entire state of the simulation is stored in a single immutable data structure
- State updates are represented as functions that map a state to the next state
This means that I could write an update function as something like:
(defn example-update-function [old-state]
(let [state (atom old-state)]
(swap! state some-other-update-function-1)
(if (some-condition @state)
(swap! state some-conditional-update-function))
(swap! state some-other-update-function-2)
(reset! state (some-function @state some-other-param))
@state))
The approach seems to work but the following gives me two causes for concern:
- I'm having to use a mutable atom to manage the intermediate states... doesn't seem very functional!
- The code seems a bit ugly with all the swap!s and @state dereferences every开发者_StackOverflow中文版where.
Is there a better / more elegant approach?
You could write it like so:
(defn change-when "If (test val) is truethy, returns (fun val), else returns val" [val test fun] (if (test val) (fun val) val)) (defn example-update-function [old-state] (-> old-state some-other-update-function-1 (change-when some-condition some-conditional-update-function) some-other-update-function-2 (some-function some-other-param) identity))
Probably half-way to the road of monads ...
This is why the State
monad was invented, to encode state modification patterns in a way that looks idiomatic, yet under the hood uses sharing of immutable data structures. You typically get three operations, two core, one derived:
get :: m s
-- Return the state from the internals of the monad.
put :: s -> m ()
-- Replace the state inside the monad.
modify :: (s -> s) -> m ()
-- Maps an old state to a new state inside a state monad. The old state is thrown away.
So perhaps encode those primitives, to make plumbing your state easier.
Monads? Everyone's always excited about the State monad, which I gather is designed for this sort of thing.
精彩评论