On using atoms together with side effect functions

55 views
Skip to first unread message

Jozef Wagner

unread,
Jan 6, 2012, 3:02:57 PM1/6/12
to clo...@googlegroups.com
Consider this contrived piece of code:

(def aval (atom {:dumped false :contents "hello world"}))

(defn update!
  [item]
  (when-not (:dumped item)
    (spit "a.out" (:contents item) :append true)
    (assoc item :dumped true)))

How should I correctly call update! ? Each of following two calls have drawbacks.

(swap! aval update!) ;; update! can be called more than once

(let [new-val (update! aval)]
  ;; aval can be changed in between from other thread
  (reset! aval new-val))

Best,
Jozef

Dave Ray

unread,
Jan 6, 2012, 3:21:58 PM1/6/12
to clo...@googlegroups.com

Jozef,

I think you can solve this by adding a little more info and keeping
the update function and side effects separate:

(def aval (atom { :contents "hello world" }))

(defn update-item [{:keys [dumped] :as item}]
(assoc item :dumped true :needs-dump (not dumped)))

(let [{:keys [needs-dump contents]} (swap! aval update-item)]
(if needs-dump
(spit "a.out" contents :append true)))

Whoever gets to aval first while it hasn't been dumped will see
needs-dump as true and write the file. Depending on your requirements,
if the file write fails, you'll might need to do some extra work to
put aval back in a state consistent with reality.

Hope this helps,

Dave

Jozef Wagner

unread,
Jan 6, 2012, 3:56:35 PM1/6/12
to clo...@googlegroups.com
Thank you,

But the things are more complicated. In my case, I need to update the atom with the result of a (native) function which unfortunately also performs some side effects and cannot be split in two.

Updated example:

(def state {:result nil :input [1 2 3]})

(defn update-item!
  [item]
  (if (:result item) ; if already processed, do nothing
    item
    (let [result (side-effect-fn! (:input item))]
      (assoc item :result result))))

(swap! state update-item!) ;; same problem

Jozef

Alan Malloy

unread,
Jan 6, 2012, 4:34:24 PM1/6/12
to Clojure
Not doable with atoms if you have a side-effecty thing that you can't
separate out. You'll have to use something lower-level like Java
synchronization or locks.

Cedric Greevey

unread,
Jan 6, 2012, 9:16:44 PM1/6/12
to clo...@googlegroups.com

Or something higher level like transactions. Use a ref and have
update! alter the ref, then (send-off some-agent #(spit ...)), inside
a dosync. The agent send only goes if the transaction commits.

Alan Malloy

unread,
Jan 6, 2012, 10:22:10 PM1/6/12
to Clojure
On Jan 6, 6:16 pm, Cedric Greevey <cgree...@gmail.com> wrote:
This works exactly as well as with atoms: it works fine if you have a
pure function and an I/O function, but doesn't work if you have a
single function that does both pure computation and I/O side effects.
Jozef has some Java magic that does I/O as a side effect of a
computation, and he cannot untangle the two.

Cedric Greevey

unread,
Jan 6, 2012, 10:32:08 PM1/6/12
to clo...@googlegroups.com

Wrong. Jozef has this:

(def aval (atom {:dumped false :contents "hello world"}))

(defn update!
[item]
(when-not (:dumped item)
(spit "a.out" (:contents item) :append true)
(assoc item :dumped true)))

(swap! aval update!)

Which can be transformed to this:

(def aval (ref {:dumped false :contents "hello world"}))

(def aagent (agent nil))

(defn update
[item]
(dosync
(if (:dumped @item)
(ref-set item nil)
(do
(alter item assoc :dumped true)
(send-off aagent (fn [_] (do (spit "a.out" (:contents @item)
:append true) nil)))))))

(update aval)

which should have the same semantics, except that the spit cannot be
done twice (or more) for one call to update.

Alan Malloy

unread,
Jan 6, 2012, 11:28:37 PM1/6/12
to Clojure
On Jan 6, 7:32 pm, Cedric Greevey <cgree...@gmail.com> wrote:
That's the simplification he made in his first post, but if you look
at the post I was actually replying to he's confessed he actually has
a different situation.

Cedric Greevey

unread,
Jan 6, 2012, 11:40:35 PM1/6/12
to clo...@googlegroups.com
On Fri, Jan 6, 2012 at 11:28 PM, Alan Malloy <al...@malloys.org> wrote:
> but if you look at the post

There is no call for taking a rude tone. I correctly answered the
question as originally posed.

Reply all
Reply to author
Forward
0 new messages