Consider this public domain for copyright purposes.

Try it at the repl with:

(defn make-counter [] (let [i (atom 0)] #(do (swap! i + 1) @i)))
(def *counter* (make-counter))
(do-asynch-periodically *xxx* 1 (println (*counter*)))
(do-asynch-periodically *xxx* 1 (println (str "changed!
" (*counter*))))

After submitting the first do-asynch-periodically form, numbers should
start counting up in the standard output stream. Wait a while, then
submit the second. The numbers will keep counting up, with "changed! "
prepended now to each one, indicating that the previous *xxx* thread
was replaced by a new one rather than left orphaned but still running.
So if you define a daemon job with this macro, and then during
development keep reloading the file with the definition, you won't be
creating a glut of threads all trying to do the same job, or worse,
instead of replacing a buggy version with the fixed version, ending up
with both running side-by-side.

It took quite a bit of digging to figure out how to make with-global-
if-exists work, actually the trickiest part of the whole thing. :) It
might have other uses -- if *foo* exists and is bound, (with-global-if-
exists *foo* bar (do-something-with bar)) will evaluate (do-something-
with bar) with bar's value equal to *foo*s if *foo* exists and is
bound, and do nothing otherwise. So, it's like (do-something-with
*foo*) except it doesn't fail to compile if *foo* isn't defined, nor
blow up at runtime if *foo* is a var but is unbound, but instead does
nothing in both those cases. It also doesn't have side-effects -- the
var won't be def'd, or bound, by the macro expansion, unless one of
the provided arguments has such a side-effect when evaluated.

The do-asynch-periodically macro takes a name, a number, and a body.
The body will start executing asynchronously at an interval in seconds
equal to the supplied number, with the first execution occurring that
amount of time after the do-asynch-periodically form is evaluated. It
will then loop endlessly, or until stopped with (. name (interrupt))
or restarted with a new body by evaluating another do-asynch-
periodically form with the same name. The name is bound in the current
namespace when the form is evaluated, to a Java Thread object
admitting the usual manipulations, such as tweaking its priority,
interrupting it, or whatever. The thread will die after the current
body execution if interrupted, since it lets sleep throw an uncaught
InterruptedException in that case.

This could be very useful for implementing daemons to process job
queues of various sorts. The example above used a closure that
increments a counter, which is a nice demonstration but otherwise
useless; more pragmatically, you'd use refs and transactions, atoms
with something more interesting, or a BlockingQueue from the
java.util.concurrent package.

The thread macro, and the techniques in the other macros, could also
be used to implement a periodically fn that creates jobs at runtime
from passed-in functions, e.g. (periodically #(println (*counter*)))
would return a thread that, when started, printed increasing numbers.
(Being a macro, do-asynch-periodically is really only suitable for
defining a fixed set of global job-daemons that will always be part of
the background when your software is running.)

On a side note, I've noticed a few issues with clojure that could use
a bit of sprucing up, no disrespect intended:

* If REPL code goes into infinite loop, only escape is to reset the
REPL,
  which loses everything. Perhaps make ctrl-C interrupt and return to
  prompt? It could throw InterruptedException into the running code,
or
  Error, or something.
* No apparent way to change imports on-the-fly in REPL, have to
  recompile all dependencies, close REPL, and reopen REPL. Tedious.
* Compiler diagnostics could use some improvement. Many are cryptic,
  not clearly indicating the nature of the original problem. Line
number
  isn't always given if a REPL expression triggered the exception,
even
  if the actual error is in a .clj file. Given many functions and
  expressions on some lines, column number would be useful too.
* One especially cryptic error: undefined symbol clojure.core/unquote
  (or unquote-splicing). This looks like library misconfiguration but
  comes from forgetting the back-tick in some macro somewhere.
* For some reason, at the clojure web site, the back button is
  noticeably slower than normal to operate and does not remember
  my position on the previous page -- if I am at the bottom of
  "vars and environment" for example, click one of the links there
  (such as to var-set on the API page), and hit back, it takes a
  couple of full seconds to be back at "vars and environment" and
  then I'm at the top and have to scroll all the way back down.
  This is strange, since other sites (e.g. Wikipedia) don't do this.
  I'm using Firefox 3.0.10 in case it matters.
* Documentation of some useful things is still lacking there. For
  example, I discovered .hasRoot via (macroexpand-1 '(defonce
  *foo* nil)) after coming up empty browsing "vars and
  environment" and inc-searching the whole api page for "var".
  Posting the Javadocs for the classes of core Clojure objects,
  e.g. IFn, ISeq, and IPersistentCollection, would probably go
  a long way towards alleviating that, as I have no doubt that
  .hasRoot and plenty of similar things are in those. :)

And now, without further ado, I present do-asynch-periodically:

(defmacro with-global-if-exists [nm bind & body]
  `(let [v# (ns-resolve *ns* (quote ~nm))]
     (if v#
       (if (.hasRoot v#)
         (let [~bind (var-get v#)]
           ~...@body)))))

(defmacro thread [& body]
  `(Thread. (proxy [Runnable] [] (run [] ~...@body))))

(defmacro defdaemon [nm & body]
  `(do
     (with-global-if-exists ~nm t#
       (. t# (interrupt)))
     (def ~nm (let [tt# (thread ~...@body)]
                (. tt# (setDaemon true))
                (. tt# (start))
                tt#))))

(defmacro do-asynch-periodically [nm interval & body]
  `(defdaemon ~nm
     (loop []
       (Thread/sleep (* 1000 ~interval))
       ~...@body
       (recur))))

--~--~---------~--~----~------------~-------~--~----~
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
Note that posts from new members are moderated - please be patient with your 
first post.
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
-~----------~----~----~----~------~----~------~--~---

Reply via email to