clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
martinklepsch 2021-03-27T13:35:34.028700Z

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

👏 1
2021-03-29T23:57:01.073100Z

slightly more compact version

(defn stateful-cycle3
  [xs]
  (let [state (atom (cycle (shuffle xs)))]
    #(first (swap! state rest))))

2021-03-27T13:43:03.028800Z

cycle is built into Clojure core library, and returns infinite (lazy) sequence that cycles through its input collection values.

2021-03-27T13:44:19.029Z

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.

2021-03-27T13:45:47.029200Z

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.

2021-03-27T13:47:41.029400Z

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!

2021-03-27T13:52:56.029600Z

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

2021-03-27T13:53:59.029800Z

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.

2021-03-27T13:54:36.030Z

"better" in the sense that the function is useful in more situations, because it does not force the shuffle behavior.

martinklepsch 2021-03-27T15:36:36.037100Z

Wow Andy, that’s great feedback all around! Good point about shuffle as well

martinklepsch 2021-03-27T15:50:20.037300Z

thread safety wasn’t really a concern since it’s JS but I like how your usage of next just simplifies the whole thing

chrisn 2021-03-27T16:58:03.037600Z

Another jvm-specific formulation that may be interesting:

(let [state-iter (.iterator ^Iterable (cycle xs))] 
  #(locking state-iter
     (.next state-iter)))

seancorfield 2021-03-27T19:18:16.038800Z

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

seancorfield 2021-03-27T19:18:40.039100Z

Now I can clean up a bit more code at work! 🙂

slipset 2021-03-27T19:19:38.039500Z

You’re welcome!