Rhywun (26) [Avatar] Offline
#1
When I follow along with the sample code, I get false after executing this form - not true:

(compare-and-set! total-rows 200 250)


Even though dereferencing total-rows gives 200.

Any ideas?
Francis Avila (16) [Avatar] Offline
#2
This is due to an ugly Java implementation detail around boxed numbers. compare-and-set! uses identity (Java ==) for the comparison, so a comparison of two boxed numbers which are different objects will fail even if they have the same value. This problem does not show up with smaller numbers because Oracle and OpenJDK will intern (i.e., ensure there is only one shared instance of) small numbers from -127 to 128. This range is both configurable from the command line, and may vary by JVM implementation.

So in summary:

  • compare-and-set! takes Objects, so its numeric arguments are autoboxed by the JVM.

  • If the numbers are small enough, the autoboxing produces the same (identical) object.

  • compare-and-set! checks the old value using identity (not equality) comparison. This works as expected for small numbers due to interning.

  • But once the number gets big enough, the autoboxed number is not interned and you may have two different objects.


  • Observe:

    (def two-hundred 200)
    => #'user/two-hundred
    (def a (atom two-hundred))
    => #'user/a
    (compare-and-set! a 200 300)
    => false
    (compare-and-set! a two-hundred 300)
    => true
    


    I have added more detail to a note on the ClojureDoc page for compare-and-set!
    Rhywun (26) [Avatar] Offline
    #3
    That explains why the example I also found there worked but your sample code didn't. Thanks!