Hi Rich, Wow, that's a comprehensive answer. Thanks.
I am using runonce to create lancet, a build system that ties into Java's Ant. Build steps definitely do have side effects, so I will probably end up using the agent approach. Targets don't have (or ignore) return values, so I could ignore the return entirely. Have to think about that some more. Java programmers will suspect that the peek optimization doesn't work because of its structural similarity to double-checked locking, so that's going to be fun to explain. :-) Stuart > On Nov 9, 8:21 am, Stuart Halloway <[EMAIL PROTECTED]> wrote: >> You should be able to do this without the ref. Have the agent's state >> contain a pair of [has-run, fn-result]. > > The semantics of your runonce aren't clear to me, but here are some > strategies: > > As Chouser proposed, if you only want a one-time effect, atomics are > the simplest. I prefer to use compareAndSet so it's clear what the > governing transition is, and I've written this more verbosely so it > corresponds to the other solutions: > > (defn runonce > "Create a function that will only run once. All other invocations > return nil" > [function] > (let [first-call (Object.) > atom (java.util.concurrent.atomic.AtomicReference. first- > call)] > (fn [& args] > (when (.compareAndSet atom first-call nil) > (apply function args))))) > > If you don't want any callers to proceed until it has been run once, > then they'll need to queue up somehow. You can use either transactions > or agents, and which to choose depends on whether or not the function > has side-effects. If it doesn't, you can use transactions, the > benefits being multiple such calls can compose: > > (defn runonce > "Create a function that will only run once. All other invocations > return the first calculated value. The function must be free of side > effects" > [function] > (let [first-call (Object.) > ret (ref first-call)] > (fn [& args] > (dosync > (when (= @ret first-call) > (ref-set ret (apply function args))) > @ret)))) > > If the function has side effects, agents are the way to go: > > (defn runonce > "Create a function that will only run once. All other invocations > return the first calculated value. The function can have side > effects" > [function] > (let [first-call (Object.) > agt (agent first-call)] > (fn [& args] > (send-off agt > #(if (= % first-call) > (apply function args) > %)) > (await agt) > @agt))) > > Note that there is no magic bullet here - the agents approach does not > compose, each such function must run autonomously (that's not bad, > just must be understood). This is enforced by the fact that await will > fail if called in an action. > > Note also the use of a private sentinel value in order to avoid > managing both the return and the flag. > > Both the ref and agent solutions support a 'peek' optimization to > avoid the transaction/action-send: > > (defn runonce > "Create a function that will only run once. All other invocations > return the first calculated value. The function must be free of side > effects" > [function] > (let [first-call (Object.) > ret (ref first-call)] > (fn [& args] > (when (= @ret first-call) > (dosync > (when (= @ret first-call) > (ref-set ret (apply function args))))) > @ret))) > > (defn runonce > "Create a function that will only run once. All other invocations > return the first calculated value. The function can have side > effects" > [function] > (let [first-call (Object.) > agt (agent first-call)] > (fn [& args] > (when (= @agt first-call) > (send-off agt > #(if (= % first-call) > (apply function args) > %)) > (await agt)) > @agt))) > > You must still re-examine the flag inside the transaction/action. > > If you want runonce to return both the once-running function and a way > to detect if it has been run, I recommend returning a has-run fn which > encapsulates the mechanism: > > (defn runonce > "Create a function that will only run once. All other invocations > return the first calculated value. The function must be free of side > effects. > Returns [has-run-predicate once-fn]" > [function] > (let [first-call (Object.) > ret (ref first-call) > has-run #(not= @ret first-call)] > [has-run > (fn [& args] > (when-not (has-run) > (dosync > (when-not (has-run) > (ref-set ret (apply function args))))) > @ret)])) > > You can do something similar for the agent version. > > Rich > > > --~--~---------~--~----~------------~-------~--~----~ 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 [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/clojure?hl=en -~----------~----~----~----~------~----~------~--~---