I couldn't resist writing some more macros!

(defmacro << [k-or-ks f]
  `(fn [state#]
     (update-in state#
                ~(if (vector? k-or-ks)
                   k-or-ks
                   (vector k-or-ks))
                ~f)))

(defn >>* [state form]
  (condp = (first form)
    '>>> `(fn [~state] ~@(rest form) ~state)
    '<<< `(fn [~state] ~@(rest form))
    form))

(defmacro >> [state & forms]
  (reduce #(list (if (#{'<< 'fn 'fn*
                        'clojure.core/fn
                        'clojure.core/fn*} (first %2))
                   %2
                   `(fn [s#] ~%2 s#)) %)
          (conj (map (partial >>* state) forms)
                state)))

With those the resulting code looks pretty clean and remain purely 
functional:

  (next-phase [state]
    (>> state
      (stop-timer)
      (<< :phases #(conj (vec (rest %)) (first %)))
      (>>> (log :info "change phase to %s" (-> state :phases first key)))
      (<<< (start-phase state))))

Now, I'd need to find better names!

On Sunday, April 15, 2012 11:25:21 PM UTC-4, Nicolas Buduroi wrote:
>
> I'm working on a turn-based game and I'm looking for a good way to manage 
> states. In the game each turn is composed of multiple phases. I started by 
> using atoms for the phases field (this is a sequence of functions) in a 
> record and realized that it wouldn't be ideal to keep track of states in 
> the case where I'd need to keep a snapshot of every phases. Here's the 
> original code I had:
>
> (defrecord Game [phases]
>   (next-phase [this]
>     (stop-timer)
>     (swap! phases #(conj (vec (rest %)) (first %))) 
>     (log :info "change phase to %s" (key (first @phases)))
>     (start-phase this))
>
> I then started to think that this would be a good opportunity to use a 
> state monad. I've tried to reimplement the above code using the algo.monads 
> library but the result was less than satisfactory (probably due to my own 
> shortcoming), here's the monadic version:
>
> (defrecord Game [phases]
>
>   (next-phase [this]
>     (->
>      ((domonad state-m
>         [_ (fn [s] (stop-timer) [s s])
>          _ (update-state
>             (fn [s]
>               (update-in s [:phases]
>                          #(conj (vec (rest %)) (first %)))))
>          _ (fn [s]
>              (log :info "change phase to %s" (key (first (:phases s)))) [s 
> s])]
>         nil)
>       state)
>      second
>      start-phase))
>
> As my code probably doesn't need the full power of the state monad, I 
> tried to write a lighter-weight version using the following macro:
>
> (defmacro >> [& state-and-forms]
>   (reduce #(list (if ('#{fn fn*} (first %2))
>                    %2
>                    `(fn [s#] ~%2 s#)) %)
>           state-and-forms))
>
> Which let me write:
>
>   (next-phase [state]
>     (>> state
>      (stop-timer)
>      (fn [s] (update-in s [:phases] #(conj (vec (rest %)) (first %))))
>      #(do (log :info "change phase to %s" (key (first (:phases %)))) %)
>      #(start-phase %)))
>
> With some more helper macro this version looks promising. In the end I 
> wonder if there's some Clojure feature I'm overlooking or if I should 
> rethink the whole solution? Is there a better way to accomplish this?
>
>
>

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

Reply via email to