> On Dec 30, 2008, at 6:29 PM, Mark Engelberg wrote: > Use Case #1: Implementing classic imperative algorithm (GDC)
I replaced your (atoms) using (with-local-vars) and the function runs perfectly fine. The local vars are not closed over, so they cannot leak, and the code is cleaner. So this example isn't a case for atom- set in my opinion. > Use Case #2: setting up mutually referential data structures The visible choices are ref and atom. Atoms are non transactional/coordinated. Atoms can only be set by providing a modifying function. Lets look at an imaginary example if you were able to set them: (atom-set a (+ @a (* @a @a))) The value of @a can actually change halfway through evaluating (+ @a (* @a @a)) giving a misleading result. Consistency is maintained but the semantics are deceptive. The atom could be set to any combination of old value and new value run through that function. atom-set encourages transactional style statements without their guarantees. Instead there is swap! which reads the current value, applies the function to it, and will only commit the result if the atom has not changed. If another thread has changed the value, it will retry until successful. (swap! a + (* @a @a)) is just as deceptive. Because swap! is a function, (* @a @a) is evaluated before swap! is called, resulting in the exact same race condition described with atom-set. It is unnecessary to use @a with swap! because the correct value of the atom to use is passed in to the function you provide to swap! The correct way to write this particular example would be: (swap! a #(+ %1 (* %1 %1))) It is very easy to define an efficient atom-set: (defn atom-set [a val] (.set a val) val) But there is no way to prevent users doing things like (atom-set a (inc @)) which is incorrect. Neither can swap! prevent users from dereferencing the target atom, however there is a clear distinction in the access method which reminds and encourages correct usage. > It would be entirely reasonable to want to, under certain conditions, > to reset the counter to 0. When you reset to 0, that's just a > straight write. Having to say (swap! counter (constantly 0)) feels > convoluted, and somewhat obscures the fact that this change is not > dependent on the previous state of the atom. Furthermore, by having > to represent this reset as a swap, the counter reset might not happen > as quickly as one would like. If there is a lot of contention and > other threads are in the process of incrementing the counter, the > reset might fail to go through for a while, because the value is > frequently changed by other threads between the unnecessary read and > the write of 0. An atom-set would not only reflect the intentions of > the code better, but would provide better performance for the times > where a read is irrelevant to the value being written. (.set a 0) Provides precisely the behavior you describe. Unfortunately It is not obvious that this is the case unless you look up the java docs. http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.html Perhaps the swap! doc string should explicitly describe the danger: "The function passed to swap should not enclose the current atom value as this may create a race condition, instead it should always take the atom value as an argument. There is no ref-set as it would likely promote such usage. You can set atoms by calling the java .set method, but remember that the current atom value should not be used to set a new value." On Dec 31 2008, 2:19 pm, Pinocchio <cchino...@gmail.com> wrote: > 1) It is possible to get "internally" mutable state using > with-local-vars and var-set. The problem here is that the "internal" > state cannot be operated on my multiple threads concurrently. Thus > functions defined this way will not automatically scale on multi-cores. > One may have to be careful while wrapping such state in a closure and > accessing it from multiple threads. However, they *will* allow to > "naturally" write certain algorithms containing local mutation. (with-local-vars) has no multi-threading issues, as they are not shared in any way. To make this true they are not retained in closures: user=> (def f (with-local-vars [a 2] #(+ 1 (var-get a)))) #=(var user/f) user=> (f) java.lang.IllegalStateException: Var null is unbound. (NO_SOURCE_FILE: 0) > 2) It is possible to get "internally" mutable state using clojure arrays > and recur style re-binding. The problem here is that recur style > rebinding may not be expressive enough to "naturally" encode certain > algorithms. Clojure does require you to be explicit about state mutation, but in my view that does not diminish the expressiveness. I'm yet to come across a case where an algorithm can not be directly translated retaining imperative style. > 3) It is possible to get "internally" mutable state using atoms (along > with atom-set for better readability and convenience)... which seems > like a nice suggestion. The problem here is the generic problem with > atoms which should be used carefully under do-sync. Particularly, if an > atom is indeed an "internal" state in a function's closure, and the > closure itself is used in a transaction then we have problems due to > transaction retries. So, we either use atoms assuming that the function > closures *will* be retried in transactions which limits the situations > in which atoms can be used or we make sure we document such function > closures so that they are not used in do-sync (may be clojure can print > a warning if it sees an atom being used inside a do-sync). "problems due to transaction retries" is not atom specific, but does indeed highlight how atoms could be used with unexpected consequences. I would say that ref is the mutable with least surprising semantics, so a good default choice unless one have specific goal in mind. > However, this problem occurs only if there is a closure. If the function > is retried and the internal state is created afresh then do we still > have a problem? Is this a valid way to handle "internal" mutable state > which is not encapsulated in closures? Correct, a mutable can only 'leak' from a function if it is returned or captured in a return... ie: a closure formed on it. Hence for unclosed mutation I think with-local-vars is the nicest. Why have (binding) and (set!) when (with-local-vars) and (var-set) achieves the same thing in a more explicit fashion? set! is still required for java interop of course, and I guess (binding) has uses that I don't fully appreciate yet. Regards, Tim. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en -~----------~----~----~----~------~----~------~--~---