Here's an example of using a state monad for updating a position. The state goes into a simple map and there's a function to add coordinates.
(def init {:position [100 100] :st :st0 :keys-held #{:left}}) (defn v+ [v1 v2] (vec (map + v1 v2))) The state monad can compute a value and maintain some arbitrary state. In this case, the statements produce side-effects but nothing is actually computed, which is fine. You'd want to model each statement to encapsulate some action and return a State instance. For simplicity, the function move mixes the conditional statement with the then-branch, but that can be easily separated later: ;; shameless plug (use 'blancas.morph.core 'blancas.morph.monads) (defn move [key v] (monad [held (gets :keys-held)] (if (held key) (modify-state #(update-in % [:position] v+ v)) (state :empty-stmt)))) This function takes the key that was pressed. If it's one held in storage, the state will be modified pretty much the way it was before; otherwise the statement evaluates into a state instance whose value is the empty statement. Each of the following elements models a conditional statement: if x do this: (def stmts [(move :left [-10 0]) (move :right [ 10 0]) (move :up [ 0 -10]) (move :down [ 0 10])]) This "runs" the sequence of monads and returns the value of the resulting state (not the value since it's not computing anything: (exec-state (seqm stmts) init) ;; {:position [90 100], :st :st0, :keys-held #{:left}} Here we do the same but then change the key and re-evaluate the statements. The combinators seqm and >> are similar; seqm takes a collection. (exec-state (>> (seqm stmts) (modify-state assoc :keys-held #{:down}) (seqm stmts)) init) ;; {:position [90 110], :st :st0, :keys-held #{:down}} This shows a computation; say you want to compute: [50 50] + [12 -5] For this you'd write a new version of v+ that takes "monadic" args (boxed in a State). As above, the (monad) macro binds the results of monads to the variables, as in a let. Then wraps the result in a state: (defn v+ [v1 v2] (monad [x v1 y v2] (state (vec (map + x y))))) (This function could take simple vectors, but in a real use case you'd be taking expressions, not just values.) Now you can get the result like so: (eval-state (v+ (state [50 50]) (state [12 -5])) init) ;; [62 45] If you want both the computed value and the finate state you can get them both in a Pair: (run-state (v+ (state [50 50]) (state [12 -5])) init) ;; Pair([62 45],{:position [100 100], :st :st0, :keys-held #{:left}}) On Monday, February 11, 2013 12:10:24 PM UTC-8, JvJ wrote: > > I'm writing a simple game engine in Clojure, and each game object supplies > its own unique update function, which takes the original object (a map of > properties) and returns an updated version. However, writing the updates > is somewhat cumbersome because each line of code has to return either the > original or updated object. I'd like to see if I can clean up this code, > possibly by using monads (which I don't understand very well). Does anyone > have any advice? Thanks (Code examples below) > > The pseudocode for what i want to do looks something like this: > > if left key is held > g.position += [-10 0] > if right key is held > g.position += [10 0] > if up key is held > g.position += [0 -10] > if down key is held > g.position += [0 10] > if q is pressed > fire event {:type :dialogue, :text "Hello"} > if space is pressed > g.switchstate(:s2) > > > But the code I ended up writing is this mess: > > > (fn [g] > (-> g > (#(if (@*keys-held* :left) > (update-in % [:position] v+ [-10 0]) > %)) > (#(if (@*keys-held* :right) > (update-in % [:position] v+ [10 0]) %)) > (#(if (@*keys-held* :up) > (update-in % [:position] v+ [0 -10]) %)) > (#(if (@*keys-held* :down) > (update-in % [:position] v+ [0 10]) %)) > (#(if (@*keys-pressed* \q) > (do (fire-event {:type :dialogue > :text "Hello!"}) > %) > %)) > (#(if (@*keys-pressed* :space) > (do (comment (println "spaced!")) > (switch-state % :s2)) %)))) > -- -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.