2
votes

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 everywhere.

Is there a better / more elegant approach?

3
What triggers a change in the state? - ponzao
It's not clear why you're changing the state atom here, or why you're using an atom at all. Are the functions you're calling reading it somehow? - seh
@seh: yes the functions can potentially read the atom and will need the latest version of the state (i.e. can't just rely on old-state since since is the potential for sequential effects in the simulation) - mikera
@ponzao: could be anything, but the most common triggers would be a) some time elapsed and the simulation needs to be update or b) a change is triggered by an external interaction, e.g. the user changing one of the simulation parameters or directly modifying some part of the simulation - mikera

3 Answers

5
votes

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 ...

3
votes

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.

1
votes

Monads? Everyone's always excited about the State monad, which I gather is designed for this sort of thing.