Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to remove item in a list for an atom in Clojure

I'm using server.socket to stream data to multiple clients, server.socket uses Threads for each client connection. I currently have something like this:

(def clients (atom ())) ; connected clients defined globally for that namespace

(swap! clients conj a)  ; adds a client (which is an atom itself as well), this is in a function that is run on the client's thread

;I want to better the process of removing a client!
(dosync (reset! clients (remove #{a} @clients))) ; removes client from list, run in a function on the client's thread

I run a function which runs through each client and grabs the content, it is in an infinite loop on each of the multiple client threads, so it's run simultaneously:

(doseq [c @clients]
  (print ((deref c) :content))
  (flush))

I sort of came to the conclusion using Atoms in the threads really makes the program work smoothly and allows non-blocking Reads, so I'm happy with this except I feel that resetting the global client's Atom just so I can remove a single client from the list is a bad move. Is there a more appropriate way to accomplish this using swap! ? I chose list for the clients atom as I'm running doseq on each connected client to grab the content and flush it to the output stream socket.

like image 515
scape Avatar asked Jul 19 '13 12:07

scape


2 Answers

Avoid deref-ing the atom within a swap! or reset!.

Here swap! is going to give you what you need. It takes a function that receives the current value, which you can use for your update:

(def clients (atom '(:a :b :c :d)))
(swap! clients (fn [s] (remove #{:a} s)))

You may be used to not seeing the function argument of swap! as explicitly as above because swap! will apply the function to any additional args provided, so if they are in the correct order, e.g. if we used set for clients, we can

(def clients (atom #{:a :b :c :d}))
(swap! clients disj :a)
like image 187
A. Webb Avatar answered Nov 15 '22 08:11

A. Webb


Sure, you can use swap!, see A. Webb's answer.

You might want to consider whether it's the best choice to store you clients in a list; a set or a map would be a more natural choice (for use with disj / dissoc). (Unless there is always a very small number of clients, in which case it may make sense to use the least complicated data structure available.)

Also, the dosync does nothing here. dosync is for use with Refs (and alter, commute, ref-set, ensure).

I'll also point out that if you run a loop like this:

(doseq [c @clients]
   ...)

then it'll always loop across the value of clients at the time the doseq form was entered, regardless of any swap!s to the clients Atom which might have occurred in the meantime. Not that it's likely to be a problem, just something to keep in mind.

Another thing to keep in mind is that Clojure's reference types are designed to (1) hold immutable data, (2) be updated with pure functions (in swap! / alter / send and friends). Putting Atoms in Atoms breaks (1); it may not be a problem here, but it's your responsibility to make sure it isn't. (Breaking (2) would be a problem pretty much always, except in special cases like debug traces which you totally want to be printed again on failed CAS etc.)

like image 37
Michał Marczyk Avatar answered Nov 15 '22 08:11

Michał Marczyk