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 -~----------~----~----~----~------~----~------~--~---