I just wrote this to cycle through a list of values in a stateful way, would be curious if others have better ideas on how to implement this. I think there might be a way with keeping a reference to a lazy seq but I don’t know if that’s a good idea and also I’m not sure how that would be done
(defn stateful-cycle [xs]
(let [state (atom (shuffle xs))]
(fn []
(let [r (first @state)]
(reset! state (conj (vec (rest @state)) r))
r))))
slightly more compact version
(defn stateful-cycle3
[xs]
(let [state (atom (cycle (shuffle xs)))]
#(first (swap! state rest))))
cycle
is built into Clojure core library, and returns infinite (lazy) sequence that cycles through its input collection values.
If you want a stateful function that returns the elements in a cyclic order on multiple subsequent calls, you could put the return value of cycle
into an atom, and call first
and rest
/ next
on that sequence on each call.
There would be no difference in behavior from the implementation you show, but an implementation using cycle
would probably be more efficient in CPU cycles than yours, where you are building a vector of all elements of the cycle as a vector using vec
on every call.
The implementation you show is not safe to call from multiple threads, since it reads the current atom value twice, and modifies it once, with no synchronization. If you needed something that could be used from multiple threads correctly, you could make a pure function that takes the current value of the cycle, and returns the next value of the cycle, and call that function on the atom via swap!
Here is an implementation that should be safe to call from multiple threads, and uses cycle
for better efficiency (should be O(1) time per call, rather than O(n) where n is the number of elements in xs
):
(defn stateful-cycle2 [xs]
(let [state (atom (cycle xs))]
(fn []
(let [next-cycle (swap! state next)]
(first next-cycle)))))
I did not include the call to shuffle
like yours does. In terms of API, it seems better to me to allow the caller to make the decision to shuffle the sequence, or not, e.g. by calling (stateful-cycle2 (shuffle xs))
if they want randomization of order, or call (stateful-cycle2 xs)
if they do not.
"better" in the sense that the function is useful in more situations, because it does not force the shuffle
behavior.
Wow Andy, that’s great feedback all around! Good point about shuffle
as well
thread safety wasn’t really a concern since it’s JS but I like how your usage of next
just simplifies the whole thing
Another jvm-specific formulation that may be interesting:
(let [state-iter (.iterator ^Iterable (cycle xs))]
#(locking state-iter
(.next state-iter)))
Thank you @slipset:
(! 1012)-> clj -Sdeps '{:deps {org.clojure/data.json {:mvn/version "2.0.2"}}}' -A:1.11
Clojure 1.11.0-alpha1
user=> (require '[clojure.data.json :as json])
nil
user=> (json/write-str {:a 1 :b 2})
"{\"a\":1,\"b\":2}"
user=> (json/write-str {:a 1 :b 2} :value-fn (fn [_ v] (inc v)))
"{\"a\":2,\"b\":3}"
user=> (json/write-str {:a 1 :b 2} {:value-fn (fn [_ v] (inc v))}) ; Clojure 1.11 calling style
"{\"a\":2,\"b\":3}"
user=>
Now I can clean up a bit more code at work! 🙂
You’re welcome!