How to Better Iterate over State in Clojure (monad

2019-04-26 20:22发布

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

3条回答
够拽才男人
2楼-- · 2019-04-26 21:05

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! :)

查看更多
狗以群分
3楼-- · 2019-04-26 21:21

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/

查看更多
兄弟一词,经得起流年.
4楼-- · 2019-04-26 21:25

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

查看更多
登录 后发表回答