Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Better Iterate over State in Clojure (monad?)

I just wrote this code:

(defn parameters [transform-factory state]
  (lazy-seq (let [[r1 state] (uniform state)
                  [r2 state] (uniform state)
                  [t state] (transform-factory state)]
              (cons [t [r1 r2]] (parameters transform-factory state)))))

(defn repeated-transform [mosaic n transform-factory state]
  (reduce transform-square mosaic
    (take n (parameters transform-factory state))))

the parameters function generates a lazy sequence of values generated from the state, which are used to parameterise a repeated transformation of something (a "mosaic" in this case).

it seems to me that parameters shows a fairly common pattern which surfaces when you have some state that must be carried around (in this case to generate random values). is there a name for this?

is there a better way to write the first function? related problems can often be solved with reduce, which "carries along" the state, but here i have nothing to reduce. similarly, reductions doesn't seem to fit. is this a good case for a monad? (from a theoretical pov i don't see how you define a way to combine multiple instances into one, but perhaps that doesn't change the practical application - it does seem like the kind of problem monads solve elsewhere, where some state needs to be carried around).

(ps i mentioned random numbers, but i can't replace this with a solution that uses mutable state behind the scenes - as "normal" random routines do - for reasons unrelated to the question).

like image 715
andrew cooke Avatar asked Apr 26 '12 16:04

andrew cooke


3 Answers

You could certainly look at the state monad to see if it is a good fit for you.

General guidelines to use monads are:

  • Sequential execution (pipeline operations)
  • Reusable modular side effect processing (ex: error handling/ logging/ state)
  • Keep your business logic clean in pure functions

Some resources on monads that I found very useful (for Clojure) are

Adam Smyczek: Introduction to Monads (Video) http://www.youtube.com/watch?v=ObR3qi4Guys

and Jim Duey: Monads in Clojure http://www.clojure.net/2012/02/02/Monads-in-Clojure/

like image 157
carinmeier Avatar answered Nov 03 '22 02:11

carinmeier


[answering myself, since this is the best solution i've found so far]

you can rewrite the above as a fold over the functions. so the functions become data, the state is the "passed through", and the function used applies each function in turn to the state and accumulates the result.

i can't see an elegant way to implement this - the function that is folded appears to be "new" and you need extra boilerplate to add/separate the state and accumulator - so i wrapped the whole process in a function called fold-over. the source is here and an example of the function in use is here.

like image 40
andrew cooke Avatar answered Nov 03 '22 02:11

andrew cooke


Something you should check into is -> and ->>, the threading macros.

Instead of code like this:

(let [state (dosomething state)
      state (dosomethingelse state)
      state (dolastthing state)]
    state)

You can write:

(-> state (dosomething) (dosomethingelse) (dolasttthing))

which "threads" state through the functions, eventually returning it.

Now, your code doesn't exactly follow what I've written. The way I imagine it could follow it was if your functions took and returned hashmaps. i.e. (uniform state) could return {:state state-val :r1 r1-val}.

Then you could rewrite your code like this:

(->> {:state state} (merge uniform) (merge uniform) (transform-factory))

Much nicer! :)

like image 31
thedayturns Avatar answered Nov 03 '22 00:11

thedayturns