Is there a better way to update a map atom?

319 views
Skip to first unread message

JvJ

unread,
Jan 21, 2013, 3:21:03 PM1/21/13
to clo...@googlegroups.com
I'm updating a set of objects stored in a map, and I feel like the way I'm doing it right now is inefficient.  Is there a better way?

(defn update-all-objects
  "Updates all game objects."
  (reset! *game-objects*
          (into {}
                (for [[k v] @*game-objects*]
                  [k (update-object v)]))))

Jim - FooBar();

unread,
Jan 21, 2013, 3:24:19 PM1/21/13
to clo...@googlegroups.com
use reduce-kv on the original map to save some intermediate vector
allocation...something similar has come up recently...

(reduce-kv #(assoc % %2 (update-object %3)) {} @game-objects)

Jim
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient
> with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Jim - FooBar();

unread,
Jan 21, 2013, 3:28:39 PM1/21/13
to clo...@googlegroups.com
...or you can go all the way, skipping reset! completely:

(swap! game-objects (fn [objects] (reduce-kv #(assoc % %2 (update-object
%3)) {} objects) ))

Jim

ps: I've not tested this but i don't see why this approach wouldn't work...

JvJ

unread,
Jan 21, 2013, 3:33:55 PM1/21/13
to clo...@googlegroups.com
That's it!  Thanks.

Jim - FooBar();

unread,
Jan 21, 2013, 3:35:24 PM1/21/13
to clo...@googlegroups.com
also, i'm sure it's a typo, but the fn you originally posted is missing its argument vector...it won't even compile like that...

Jim

JvJ

unread,
Jan 21, 2013, 3:54:27 PM1/21/13
to clo...@googlegroups.com
Yeah I cought that.  Thanks, though.

Timothy Baldridge

unread,
Jan 21, 2013, 4:03:31 PM1/21/13
to clo...@googlegroups.com
Something like this seems a bit cleaner:

(defn apply-values 
  "Applies f to every value in m and passes f zero or more args"
  [m f & args]
  (reduce (fn [accum [k v]]
               (assoc accum k (apply f v args))
              {}
              m))

and then:

(swap! *game-objects* apply-values update-object)

With this you get the bonus of a) cleaner code and b) being able to re-use your apply-values function. 


Timothy

“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Meikel Brandmeyer

unread,
Jan 21, 2013, 4:23:31 PM1/21/13
to clo...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

And since you can't have enough ways to go to Rome…

(defn update-values
[m f & args]
(reduce #(update-in %1 [%2] f args) m (keys m)))


-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.18 (Darwin)
Comment: GPGTools - http://gpgtools.org
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQIcBAEBAgAGBQJQ/bHSAAoJEM2sO9pIirXjIQcP/1DgVqGbXom2nt20OEvEXSG0
RRIr6kasR5WCEx8ceoUcvwULAC7dnWOsuFXmISV/NLsVEPRI8yWIuFg4+z5Jg3hN
usu3Lr6OLcdCcKJ4GbAaEho4vPI+tZP9CtkKpip0+ev+Gs27mB7KU+onuska+hwG
TFCD3qBtvRVtexVBGxLnSlQkj6UsKnqecYEa7aKG/N8szCHEZMN5Czu0uaGTsSmc
iDtXTkgRUdGomDD/hRSsx6csQQAn3Bs2ylajZqJcqNWbC2i0dC9upqXYA2TtkmsU
xY5ersmuGr9h2cfl2rPyyd8+QA6VYbEQASqZ3SYc98VE7t4QeQvaLzZgtzXtgcn1
z/YU685PmX2tt+b/+gZfRyA7XuPp+NDVv6+vcIHvNQFfP1CoJ3idvs0Icwqj5DsK
s6l2ZxeYp7bxOowJj7ildAZyXPqxxbna2j9pESmgMPCPRxR2DecXzyrra6LbOZIj
dz7NXXs8btPD4H2Dg6AxMZMrVkFQRIML5tKEATruxz5lCNvdCNO/IfYWiph1qXTq
uCdBLqr5YRWlxCTA6U0zSJsIitJGRaM/rrHHGd9XXHS2I0OmXZNbdI0/qFG2AwbM
YF60pEAOSAnZhP+yKR7X+XrNfAuskrIq8ao0TMEzFPV11ywX7Pexbyu3P+o1xogj
AoIPqAKFTsE37RLxU8v+
=7xwM
-----END PGP SIGNATURE-----

Meikel Brandmeyer

unread,
Jan 21, 2013, 4:29:12 PM1/21/13
to clo...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Oops. Add a apply in front of update-in.

Am 21.01.13 22:23, schrieb Meikel Brandmeyer:
> And since you can't have enough ways to go to Rome�
>
> (defn update-values [m f & args] (reduce #(update-in %1 [%2] f
> args) m (keys m)))
>
>
>

-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.18 (Darwin)
Comment: GPGTools - http://gpgtools.org
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQIcBAEBAgAGBQJQ/bMoAAoJEM2sO9pIirXjVM8P/19Ainl5i2sMWT+yyRdVRK83
RJWhX9jFuDy4ee4NL33hT1bUiK8pwouQYFm6+p6Yf+B/LT6hJI/g0Gk8uHcmlrdq
OAnzT1WF/K2O470bFxY4+SHFWMjMyKO+JVrP/WhZyalCtY9rBsVSq77hEByPGttn
PAmdCYSrHa6BexaP+a0NV7+UavbOhstT+MRm2PlG0DPsbhlylRQSxTAo7o9Hsf+H
lyrP5e8YmY8fj9hynJdsrqLxFx/gEGWfMVU9tlv8zAD3ua/iejhB79qI6ZQrRnT/
f7JOYILd6GD2Ymv0kkA+mPerhPk5zwgsRZkvh9mTehF2n8xxrmiNQKD0gir8S+4M
qMYx7vjWttuGwcnQTwZYF9BpA2BhMN1rTgE3syyzlgHwa9GyWkDLXZU7d/tdQl8J
5DXELOzt37ifraSNQScMEcfexOoxFOhfHFNAFoFHpfYvPN9ZKT/9pqGwZvg2We3w
npce/zqbSPLRTq/vg+aW+VBRq2sQUQbMq25VYyQ+bMRa97OQDcNhNcTvRcZd4HKx
WNOHVtr6NAB5N5xjgEButMzhge9W4pzVbrkBRjDUVBonlfjyVLjPAAwuU+5V+fOf
0BT/S9bEfL84w9F31Lx5q3oPi3xrdNCjIDsUAWuL+eHNydJkH3nWd8FJzq8UvhBD
A9jKfknPusskjf40nzY2
=eVeD
-----END PGP SIGNATURE-----

Stephen Compall

unread,
Jan 21, 2013, 6:22:49 PM1/21/13
to clo...@googlegroups.com

On Jan 21, 2013 3:28 PM, "Jim - FooBar();" <jimpi...@gmail.com> wrote:
> ...or you can go all the way, skipping reset! completely:
>
> (swap! game-objects (fn [objects] (reduce-kv #(assoc % %2 (update-object %3)) {} objects) ))

Which also has the benefit of being safe, unlike any reset!-based update.

--
Stephen Compall
If anyone in the MSA is online, you should watch this flythrough.

László Török

unread,
Jan 22, 2013, 2:41:03 AM1/22/13
to clo...@googlegroups.com
How about

(swap! game-objects (fn [objects] (into {} (for [[k v] objects] [k (apply f v args)])))

?  

For large maps, it will use a transient map and assoc! instead of assoc, that may result in a speedup.

Laszlo

2013/1/22 Stephen Compall <stephen...@gmail.com>

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en



--
László Török

Greg

unread,
Jun 28, 2013, 11:19:18 PM6/28/13
to clo...@googlegroups.com
Can anyone explain the relationship between swap! and reset! ?

Why is using swap! in this example "safe" and using reset! not?

I've tried searching google for comparisons of the two but can't find anything, and the documentation doesn't help much.

Thanks,
Greg

Greg

unread,
Jun 29, 2013, 12:57:50 AM6/29/13
to clo...@googlegroups.com
OK, I've found something that shows how these two work when used in the implementation of a generator:


I think I understand what the problem is now:

(reset! id (inc @id))

There's a "time gap" in between the dereference of 'id' and its assignment back to 'id' (after being incremented).

With swap!, no such problem exists, because it's an atomic operation where there is no such "time gap" between dereferencing, applying a function, and setting the new value. That all takes place in on magical atomic moment.

If I've got this wrong, please let me know!

Cheers,
Greg

--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

John D. Hume

unread,
Jun 29, 2013, 7:36:19 AM6/29/13
to clo...@googlegroups.com

It's worth knowing that the "moment" is implemented via a compare-and-set, and if the value has been changed (by another thread), the fn you passed to swap! will be called again with the atom's new value.

Cedric Greevey

unread,
Jun 29, 2013, 2:45:32 PM6/29/13
to clo...@googlegroups.com
As nobody else has said this, it bears making explicit that:

* swap! is for when the new value depends, in some way, on the previous value.

* reset! is for when the new value is independent of the previous value.

If you're simply setting a flag, for example, (reset! foo true) works. If you're incrementing a counter, though, you want (swap! foo inc), etc.

Brandon Bloom

unread,
Jun 29, 2013, 8:07:34 PM6/29/13
to clo...@googlegroups.com
> Can anyone explain the relationship between swap! and reset! ?

swap! is for "CAS"


In the examples here, you're fully replacing a value, so reset! is fine... assuming that the replacement value was not derived from the existing value. If the atom was deref-ed (via deref or the @ reader macro), that value is a perception of a potentially old state. Making a decision outside of an atomic swap operation would potentially be invalidated by another thread performing a conflicting mutation.

Ben Wolfson

unread,
Jun 29, 2013, 8:21:32 PM6/29/13
to clo...@googlegroups.com
On Sat, Jun 29, 2013 at 5:07 PM, Brandon Bloom <brandon...@gmail.com> wrote:
> Can anyone explain the relationship between swap! and reset! ?

swap! is for "CAS"



How so? No comparison is done (unless it's done in the supplied function, which is entirely up to the user of swap!). There's a separate compare-and-set! function for atoms.

--
Ben Wolfson
"Human kind has used its intelligence to vary the flavour of drinks, which may be sweet, aromatic, fermented or spirit-based. ... Family and social life also offer numerous other occasions to consume drinks for pleasure." [Larousse, "Drink" entry]

Ben Wolfson

unread,
Jun 29, 2013, 8:22:35 PM6/29/13
to clo...@googlegroups.com
anyway the relationship between swap! and reset! is that (reset! atom newval) === (swap! atom (constantly newval)). (Though that isn't how reset! is implemented.)

Cedric Greevey

unread,
Jun 29, 2013, 8:27:45 PM6/29/13
to clo...@googlegroups.com

On Sat, Jun 29, 2013 at 8:21 PM, Ben Wolfson <wol...@gmail.com> wrote:
On Sat, Jun 29, 2013 at 5:07 PM, Brandon Bloom <brandon...@gmail.com> wrote:
> Can anyone explain the relationship between swap! and reset! ?

swap! is for "CAS"



How so? No comparison is done (unless it's done in the supplied function, which is entirely up to the user of swap!). There's a separate compare-and-set! function for atoms.

Er, swap! is basically

(loop []
  (let [x @a y (f x)]
    (if (compare-and-set! a x y)
      y
      (recur))))

though it actually calls a .swap method of the Atom object that has equivalent semantics.

Ben Wolfson

unread,
Jun 29, 2013, 8:52:26 PM6/29/13
to clo...@googlegroups.com
The fact that swap! can be implemented with compare-and-set! doesn't make it enlightening (or even correct) to say that swap! is CAS, especially since compare-and-swap as explicated by the linked wikipedia page is actually compare-and-set! in Clojure terms (there's no transformation by a function and it's not unconditional).

Cedric Greevey

unread,
Jun 29, 2013, 9:06:47 PM6/29/13
to clo...@googlegroups.com
Who said swap *was* CAS, rather than was implemented *in terms of* CAS? In any event, your claim that "no comparison is done unless it's done in the supplied function" is just plain wrong. Comparison *is* done, outside that function, to make sure the atom wasn't changed by another thread while the function was executing.

Or just look at the source for clojure.lang.Atom.swap():

public Object swap(IFn f) {
for(; ;)
{
Object v = deref();
Object newv = f.invoke(v);
validate(newv);
if(state.compareAndSet(v, newv))
{
notifyWatches(v, newv);
return newv;
}
}
}

The compareAndSet method of a java.util.concurrent.AtomicReference is invoked, and other than the watcher and validator stuff you can see that it's semantically the same as the Clojure loop I posted previously.

--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.

Ben Wolfson

unread,
Jun 29, 2013, 9:21:18 PM6/29/13
to clo...@googlegroups.com
On Sat, Jun 29, 2013 at 6:06 PM, Cedric Greevey <cgre...@gmail.com> wrote:
Who said swap *was* CAS, rather than was implemented *in terms of* CAS? In any event, your claim that "no comparison is done unless it's done in the supplied function" is just plain wrong. Comparison *is* done, outside that function, to make sure the atom wasn't changed by another thread while the function was executing.

"swap! is for "CAS""

The claim here is clearly not that swap! is implemented in terms of CAS (which would be very unenlightening, since the question was about the distinction between swap! and reset!, which can *also* be implemented in terms of CAS). I admit it would have been more careful of me to say that the semantics of swap! are such that whether or not any comparison is done is an implementation detail; those semantics (which are that swap! "[a]tomically swaps the value of atom to be: (apply f current-value-of-atom args)" don't specify a comparison, which is not the case with CAS. (reset! *isn't* implemented in terms of swap! or CAS, but it *could* be, as far as the semantics are concerned; if it were, I think it would still be right to say that reset! conceptually doesn't do a comparison against anything, even though, again, as an implementation detail, it could.)

Or just look at the source for clojure.lang.Atom.swap():

IOW, "the first 100 lines of Atom.java contain all the answers"?



Brandon Bloom

unread,
Jun 29, 2013, 9:28:16 PM6/29/13
to clo...@googlegroups.com
Could we please curb the pedantry?

--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/PBiSzidSIVM/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Cedric Greevey

unread,
Jun 29, 2013, 10:17:27 PM6/29/13
to clo...@googlegroups.com
On Sat, Jun 29, 2013 at 9:21 PM, Ben Wolfson <wol...@gmail.com> wrote:
On Sat, Jun 29, 2013 at 6:06 PM, Cedric Greevey <cgre...@gmail.com> wrote:
Who said swap *was* CAS, rather than was implemented *in terms of* CAS? In any event, your claim that "no comparison is done unless it's done in the supplied function" is just plain wrong. Comparison *is* done, outside that function, to make sure the atom wasn't changed by another thread while the function was executing.

"swap! is for "CAS""

Who claimed that, again? I said  "swap! is for when the new value depends, in some way, on the previous value", which is not the same thing, and then mentioned twice that it's implemented in terms of CAS.

Reply all
Reply to author
Forward
0 new messages