Reputation: 5364
Clojure newbie here, I was going through the excellent "Clojure from the ground up" posts, and tried out the last exercise in this post.
When I replace alter
with commute
, the sum is inaccurate, but I don't understand why.
(def work (ref (apply list (range 1e5))))
(def sum (ref 0))
(defn trans-alter [work sum]
(dosync
(if-let [n (first @work)]
(do
(alter work rest)
(alter sum + n)
(count @work))
0)))
(defn trans-commute [work sum]
(dosync
(if-let [n (first @work)]
(do
(commute work rest)
(commute sum + n)
(count @work))
0)))
(I've skipped the code that sets up the futures and calls them etc)
With trans-alter
here I got 4999950000
for the sum (which is the correct expected value), while with trans-commute
I got a different value each time, but higher than expected (e.g. 4999998211
).
What am I missing here? Thanks in advance!
Upvotes: 4
Views: 252
Reputation: 91554
Commute and alter essentially do the same thing, though commute is a little more lenient on the guarantee of correctness.
Alter instructs the STM to always ensure that this code ran all the way through without any of the refs it uses changing out from under it.
Commute is an instruction to help the STM decide when it needs to abort a transaction because the underlying data changed out from under it.
If everything in a transaction is commutative, then it's ok to let that transaction finish even if some data changed. In your case two transactions could both:
commute
to instruct the STM that this is OK, and it should just go ahead and commit the transaction anyway... So in short,the work you are asking to preform is not actually a commutative operation. Specifically removing an item from a list is not commutative. If you change any of the commutes to an alter, then step 4 would have kicked one of them out and only allowed one of them to finish. The one that got kicked out would be re-run on the fresh data and eventually would have arrived at a correct result.
Upvotes: 5