Suggestion 1: In the undefined-symbol error message, special-case #<CompilerException java.lang.IllegalStateException: Var clojure.core/ unquote is unbound. (NO_SOURCE_FILE:0)>
by adding a note in the specific cases of clojure.core/unquote and clojure.core/unquote-splicing saying "The most common cause of this is forgetting a backtick in a macro." Var clojure.core/anything is unbound will otherwise tend to make someone think they've got install/ classpath problems, until they figure out that backquote actually temporarily creates unquote and unquote-splicing. Suggestion 2: Fix #<CompilerException java.lang.Exception: Can't let qualified name: user/yy (NO_SOURCE_FILE:11)> to add a similar note: "The most common cause of this is forgetting to add # to a temporary in a macro". (At least Clojure won't silently produce a working, but capture-prone macro like Common Lisp will, and complains instead. Interestingly, Clojure still lets you intentionally capture by using `(let [~'yy foo]) instead of `(let [yy foo]). It just prevents it happening by accident.) Useful thing 1: (defn eager-seq [seqq] (loop [v [] s seqq] (if (empty? s) v (recur (conj v (first s)) (rest s))))) I came up with this when I had a file-handle leak in a files-line-seq function I was making to return lines one by one from one file, then the next, then the next in some set of files. Obviously, under the hood it used lazy-seq, line-seq, and a couple of atoms to hold the current file's open reader, if any, the reader atom holding nil at other times. Consuming the full sequence opens and closes every file in turn, so no leak there. Consuming only partially would leak a file handle, however, so I had an atom passed in to the function that would hold the reader, so the caller could close it afterward. Then, on top of this, of course I built a useful macro with- files-line-seq whose body would execute with some name bound to the seq and which would close that last reader after executing the body. Naturally, the real fun began when I decided for some reason I needed a few lines from a file and (with-files-line-seq (file1 file2 file3) lines (take 10 lines)) started leaking file handles. It didn't take me too long to realize why: take 10 is also lazy, so the files-line-seq was not even opening any file readers until after the with-files-line-seq macro had exited. (It checked the atom for nil before closing the referenced reader, because if the sequence was consumed completely inside the macro body, or happened to be consumed up to a file boundary, it would be nil. It also bound the atom to nil when it created it.) So I wrote eager-seq so I could change the above to: (with-files-line-seq (file1 file2 file3) lines (eager-seq (take 10 lines))) which returned a vector of ten lines, and did not leak file handles, because the files-line-seq is stepped through ten lines of the files before the macro body finishes executing this time, then the reader is closed. So, eager-seq may be useful for returning seqs derived by I/O when the I/O takes place inside a try ... finally close type construct, and also for occupying a file handle or database connection for a shorter time sometimes.] Other random useful stuff: (defn sleep [seconds] (Thread/sleep (* 1000 seconds))) (defmacro with-stack-trace [& body] `(try (eval (quote (do ~...@body))) (catch Throwable ex# (. ex# (printStackTrace))))) (defn to-file [str-or-file] (if (instance? File str-or-file) str-or-file (File. str-or-file))) (defn exists? [file] (. (to-file file) (isFile))) (defn directory? [file] (. (to-file file) (isDirectory))) (defmacro discarding-io-exceptions [& body] `(try ~...@body (catch IOException _# nil))) (defn close [stream] (discarding-io-exceptions (. stream close))) (defn list-roots [] (seq (File/listRoots))) (defn list-files [dir] (seq (. (to-file dir) (listFiles (proxy [FileFilter] [] (accept [file] (exists? file))))))) (defn list-files-and-dirs [dir] (seq (. (to-file dir) (listFiles (proxy [FileFilter] [] (accept [file] (or (exists? file) (directory? file)))))))) (defn list-files-and-dirs-recursive [dir] (let [stack (atom [(to-file dir)])] (letfn [(lfr-helper [] (lazy-seq (loop [] (if (empty? @stack) nil (cons (let [file (first @stack)] (swap! stack rest) (if (not (directory? file)) file (do (swap! stack concat (list-files-and-dirs file)) file))) (lfr-helper))))))] (lfr-helper)))) (defn list-files-recursive [dir] (filter #(exists? %) (list-files-and-dirs-recursive dir))) (defn reader-on [file] (BufferedReader. (InputStreamReader. (FileInputStream. file)))) (defn writer-on [file] (BufferedWriter. (OutputStreamWriter. (FileOutputStream. file)))) (defmacro with-reader [file bind & body] `(with-open [~bind (reader-on ~file)] ~...@body)) (defmacro with-writer [file bind & body] `(with-open [~bind (writer-on ~file)] ~...@body)) (defn set-atom! [at value] (swap! at #(do % (identity value)))) A lot of those are useful tidbits if you're doing any I/O code. The biggest, most complex thing in there generates a lazy seq of all the files and subdirectories in a subtree of the filesystem, and is a nice demonstration of lazy-seq and of one use for atoms. The with-stack-trace macro is useful at the REPL if you're getting an exception but it's not pointing you to a line in your source file. The (eval (quote (foo)) trickiness is to get a proper stack dump even if something in the expression you're trying to evaluate won't *compile*; it means the expression is *compiled* while the try block is active, rather than only *run* while the try block is active. Very useful if it's provoking a bug in a macro and you weren't getting a line number otherwise. At this point I'm contemplating adding another macro to my growing pile of utility code to deal with the for-me-now-recurrent idiom of (defn foo [args] (let [various-bindings])] (letfn [(foo-helper [] (lazy-seq ; do something ; with args ; and various-bindings (foo-helper))))))] (foo-helper)))) when implementing a seq-valued function. I should probably make a lazy- seqmaker macro for the (letfn...) part whose body is supposed to evaluate to a vector wrapping the next item or nil (the vector being so that the next item can be nil, noting that [nil] will be distinguishable from nil). Or I could get really fancy and include the bindings. For lazy seqs generated from I/O or other sources, there are usually one or two atoms that get updated each item, or at least references to streams. It would be nice if the body could dispense with all the swap!, set-atom!, and at-signs and be more like the body in between loop [bindings] and (recur new-binding-values). Having the macro create bindings to atoms and locally define (next-item item new- binding-values) to use set-atom! with the new-binding-values and evaluate to item would work here. Of course, using set-atom! instead of swap! loses the atomicity when the new value depends on the old, but since these particular atoms will be thread-local, and are really just being used as thread-local mutable references, it won't matter anyway. The tricky bit would be passing the body through a transformation to change foo to (deref foo) whenever foo was one of the bindings. A helper function might be used for that: (defmacro lazy-seqmaker [[bindings] & body] (let [fixed-body# (helper-fn body)] (let [fixed-bindings# (slap-atom-around-values bindings)] '(letfn [(~'next-item [args] some-stuff)] (let ~fixed-bindings# (letfn [(helper-fn# [] (lazy-seq (let [new-item# (do ~...@body)] (if new-item# (cons new-item# (helper-fn#))))))] (helper-fn#)))))) Heck, 1/3 of it is written already, just not helper-fn, slap-atom- around-values, or next-item. :) --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---