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

Reply via email to