2012-01-20 4 views
6

извлечение из Есть более простой способ, чтобы написать этот код в Clojure: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)) 

альтернативный способ написать это будет:

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

Это кажется еще хуже.

Во всяком случае у меня есть код, который использует атомы для очередями много и используя прежний подход делает код действительно сбивает с толку, я ожидал бы, чтобы было что-то вроде:

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

или какой-либо аналогичного механизма в замена! потому что это похоже на то, что вы хотели бы часто делать (даже не ограничиваясь очередью, я ударил несколько других случаев использования, когда было бы полезно вернуть другое значение, например, старое значение, которое было заменено) и он не разбивает атом/своп! семантика.

Есть ли способ сделать это в Clojure?

ответ

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

лол я написал CAS один первый раз, когда я столкнулся с проблемой, но думал, что это было слишком громоздким, а не рассматривать его в отделение функции - чувство довольно немой прямо сейчас :) –

+0

Обратите внимание, что вы, вероятно, не можете отличить nils от очереди и nils от пустой очереди. Проверка с 'count' до' dequeue! 'Не является потокобезопасной. Поэтому будьте осторожны с ловушками. – kotarak

+0

Yup - помните эту часть - если кто-то заботится - первое решение выше может быть изменено, чтобы проверить, присутствует ли верхний ключ, и это пустой сигнал очереди. –

3

Использование реф будет проще вариант:

(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)) 
Смежные вопросы