In Java, you often have to pair things, e.g. opening a file and
closing it, to avoid leaking resources like file handles.

These pairings are among many cases where Java code contains structure
that you can't extract and reify in your program.

Macros make it possible to extract any such pattern, reify it, and
call it. Instead of a recurring pattern of

FileInputStream x = null;
try {
    x = f.open(opts);
    doSomethingWith(x);
} finally {
    if (x != null) x.close();
}

you can convert all the crud into an abstraction and call it, like this:

(with-open [x (.open f opts)]
  (do-something-with x))

All the constant stuff that would be repeated every time you open a
file is gone; just which file, what options, what to call the stream
temporarily, and what to do with the stream remains, along with the
*fact* that you're opening some sort of a closeable stream and using
it.

Macros get you closer to the ideal of specifying every fact in exactly
one place in your code. Here that fact is "how you ensure the file you
open gets closed, including if an exception gets thrown while you're
reading from it". In Java you have to tell the compiler how you do so
over and over again at every I/O site; in Clojure you can tell the
compiler how you do so once, calling this new abstraction with-open,
and then at your I/O sites just tell the compiler with-open.

In this instance, it's already done for you; with-open is part of
core. In other instances you may spot a similar pattern of repetitive
code but one that isn't already addressed in such a manner and then
you can convert it into a callable abstraction yourself.

The big decision point is: function or macro? In fact, the basic
behavior of with-open could be implemented as a function:

(defn with-open-fn [stream f]
  (try
    (f stream)
    (finally
      (.close stream))))

and called like so:

(with-open-fn (.open some-file) #(do-something-with %))

The macro here just acts as syntactic sugar to let you avoid the #(
... % ...) closure around the body of code you want to run on the
stream, and to let you name the stream something other than just %.

In that sort of case it's good to have both the function and the
macro; the macro can expand into a call to the function while having
the nicer syntax.

You absolutely *need* a macro if you need to control whether, when,
and in what order arguments are evaluated (e.g. implementing flow
control structures).

(Interestingly, it's even possible to implement with-open's behavior
as a Java method:

public interface NullaryFn {
    Object doIt () throws Exception;
}

public interface UnaryFn {
    Object doIt (Object argument) throws Exception;
}

public class WithOpen {
    public static Object withOpen (NullaryFn streamMaker, UnaryFn fn)
throws Exception {
        Stream x = null;
        try {
            x = (Stream)(streamMaker.doIt());
            return fn.doIt(x);
        } finally {
            if (x) x.close();
        }
    }
}

final File f = whatever;
WithOpen.withOpen(
    new NullaryFn(){public doIt(){return f.open();}},
    new UnaryFn(){public doIt(Object s){return doSomethingWith((Stream)s);}});

Look at that ugly call syntax, though. This is what you get because of
a) Java lacking a decent syntax for closures, b) Java's strong typing
(now you need casts everywhere; generics lets you get rid of that at
the expense of having <T>s and <Stream>s everywhere instead), and c)
Java's checked exceptions gimmick (now everything gets contaminated
with "throws Exception;").

And your file related code is buried in just as much boilerplate junk
as before, if not even more of it.

Actually, the Clojure code turns into bytecode more or less equivalent
to what you get with the above Java. (It actually wraps exceptions at
function boundaries in RuntimeException instead of declaring throws
Exception on every method; and you may need to type-hint the .open
target to get a straight method call instead of a bunch of calls to
the Reflection API; otherwise, more or less the same.) But the Clojure
compiler lets you write closures with #(.open f) instead of new
NullaryFn{blah, blah, blah} and have global callable entities like
with-open that don't need a SomeClass. prefix tacked on, as well as
leaving out all the type names, casts, and other nonsuch.

And it lets *you* eliminate whole new classes of syntactic salt the
compiler writers couldn't have known about, by writing your own macros
that, in essence, extend the compiler. Any long repetitive structure
of code that *can't* be made much nicer and still work properly by
abstracting it into a function generally *can* be made much nicer and
still work properly by abstracting it into a macro, and sometimes a
macro can make things slightly nicer than a function (e.g., if the
function will generally be called with an inline closure as one of its
arguments, a macro with [other-args & body] parameters is good
syntactic sugar to use, preferably writing the function and then
making the macro emit a call to that function).

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