Amazon

Friday, August 24, 2012

Query and Alter with clojure agents

Today I was trying to figure out how I could use Clojure's agents to write a concurrent queue. The part I was struggling with was how do I pop a value off the queue. A pop involves not only removing an element from the queue but also returning that value.

I came up with a function called query-and-alter.

(defn query-and-alter [a query-fn alter-fn]
  (let [p (promise)
        f (fn [x]
            (deliver p (query-fn x))
            (alter-fn x))]
    (send a f)
    @p))
gist

It takes 2 functions, the first queries the value associated with the agent and the second alters the agent value.

Here's how you can use it.

eagents.core> (def my-agent (agent (sequence [])))
#'eagents.core/my-agent
eagents.core> @my-agent
()
eagents.core> (send my-agent conj :a)
#<Agent@376433e4: ()>
eagents.core> (send my-agent conj :b)
#<Agent@376433e4: (:a)>
eagents.core> (send my-agent conj :c)
#<Agent@376433e4: (:b :a)>
eagents.core> (send my-agent conj :d)
#<Agent@376433e4: (:c :b :a)>
eagents.core> @my-agent
(:d :c :b :a)
eagents.core> (query-and-alter my-agent last drop-last)
:a
eagents.core> @my-agent
(:d :c :b)
You can see we added 4 items to the queue then we popped an item off the queue. The returned value was :a and :a was removed from the queue. All in one send to the agent.

2 comments:

  1. Why not return the promise (w/o deref) so that the function doesn't block? This function eliminates the async benefit of agents.

    ReplyDelete
  2. That would work. In my case, I couldn't proceed without the value so I just went ahead and deref'd it in the function.

    ReplyDelete