8
votes

Is there a simpler way to write this code in Clojure :

(def queue (atom {:top nil :queue PersistentQueue/EMPTY}))
(swap! queue #(hash-map :top nil :queue (conj (:queue %) "foo")))
(let [{:keys [top]} (swap! queue
                        #(hash-map 
                           :top (peek (:queue %)) 
                           :queue (pop (:queue %))))]
  (println top))

alternative way to write it would be :

(def queue (atom PersistentQueue/EMPTY))
(swap! queue conj "foo")
(let [top (atom nil)]
  (swap! queue 
         (fn [queue]
           (reset! top (peek queue))
           (pop queue)))
  (println @top))

That seems even worse.

Anyway I have a code which uses atoms for queuing a lot and using the former approach is making the code really confusing, I would expect there to be something like :

(swap! queue (fn [queue] (AtomSwapResult. atom-value return-value))

or some similar mechanism in the swap! function since it seems like the kind of thing you would want to do often (not even limited to queuing, I've hit several other use cases where it would be useful to return a different value, for eg. the old value that was swapped out) and it doesn't break the atom/swap! semantics.

Is there a way to do this in Clojure ?

3

3 Answers

17
votes
(defn dequeue!
  [queue]
  (loop []
    (let [q     @queue
          value (peek q)
          nq    (pop q)]
      (if (compare-and-set! queue q nq)
        value
        (recur)))))

(def queue (atom clojure.lang.PersistentQueue/EMPTY))
(swap! queue conj :foo)
(swap! queue conj :bar)
(seq @queue)
(dequeue! queue)
(seq @queue)
3
votes

Using ref would be a simpler option:

(defn dequeue!
  "Given a ref of PersistentQueue, pop the queue and change the queue"
  [queue-ref]
  (dosync
    (let [val (peek @queue-ref)]
      (alter queue-ref pop)
      val)))

(let [q (ref clojure.lang.PersistentQueue/EMPTY)]
           (dosync (alter q conj 1 2 3)
                   (alter q conj 5))
           (fu/dequeue! q)
           => 1
           (seq @q)
           => (2 3 4 5))
1
votes

Devil's advocate, don't write this in clojure - just use the high quality Java queues that are already available.

(import java.util.concurrent.LinkedBlockingQueue)

(def queue (LinkedBlockingQueue.))

(defn dequeue! [q]
  (.take q))

(defn enqueue! [q v]
  (.put q v))