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

Reply via email to