2012-01-20 14 views
6

Czy istnieje prostszy sposób, aby napisać ten kod w Clojure:Zamień się na Clojure! atom zdejmującą z kolejki

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

alternatywny sposób byłoby napisać:

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

To wydaje się jeszcze gorsza.

Zresztą mam kod, który używa węgla do kolejkowania dużo i za pomocą byłego podejście czyni kod naprawdę mylące, będę oczekiwać tam być coś takiego:

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

lub jakiegoś podobnego mechanizmu w zamiana! działa, ponieważ wydaje się, że coś, co chciałbyś robić często (nie ograniczając się nawet do kolejkowania, trafiłem na kilka innych przypadków użycia, w których warto zwrócić inną wartość, np. stara wartość została zamieniona out) i nie łamie atom/swap! semantyka.

Czy istnieje sposób, aby to zrobić w Clojure?

Odpowiedz

15
(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) 
+1

lol pisałem CAS raz pierwszy natknąłem się na problem, ale myślałem, że to zbyt rozwlekły i nie rozważyć oddzielenie go w funkcji - uczucie całkiem głupi teraz :) –

+0

Zauważ, że prawdopodobnie nie możesz odróżnić nils od kolejki od nils z pustej kolejki. Sprawdzanie z 'count' przed' dequeue! 'Nie jest wątkowo bezpieczne. Tak więc strzeż się pułapek. – kotarak

+0

Yup - pamiętaj, że również część - jeśli ktoś ma na to ochotę - pierwsze rozwiązanie powyżej można zmodyfikować, aby sprawdzić, czy klucz górny jest obecny - i to jest pusty sygnał kolejki. –

3

Korzystanie ref byłaby prostsza opcja:

(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)] 
      (dosync (alter q conj 1 2 3) 
        (alter q conj 5)) 
      (fu/dequeue! q) 
      => 1 
      (seq @q) 
      => (2 3 4 5))