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