> I'm quite new to macros so forgive me if this is a naïve question, but
> is it possible to write macros that are applied to an entire Clojure
> program?
It depends on what you call a "program". Clojure code is structured
in the form of namespaces, and inside each namespace there is a
sequence of top-level expressions that are evaluated in order.
Launching a program written in Clojure means launching a function in
a namespace, which would typicall call other functions from several
other namespaces. Some of these would typically be part your
"application", other would be in libraries or in the Clojure core.
I don't think there is any way to apply a macro to all of that
without modifying Clojure itself. I also don't think there is a good
reason to apply a macro to everything. If you find a way to speed up
basic operations in Clojure that are of general interest, you'd
better propose a patch to Clojure than write your own compiler on top
of it.
What can be done fairly easily is apply a macro to all of a
namespace. If you macro is called "optimize", you insert the line
(optimize
right below the ns form and a line
)
at the end of the namespace. But I am still not convinced that this
is a good idea, as you would still be writing a compiler on top of
Clojure. As with all optimization, I would recommend to first
identify the parts of your program that are the performance
bottleneck, and then optimize those.
> The reason I ask is that, in other threads in this group, some simple
> transformations to improve efficiency of Clojure programs were
> mentioned. In particular, it seems converting `(+ 1 2 3)` to `(+ 1 (+
> 2 3))` can speed things up.
How much do you expect to gain from this? I didn't see a discussion
on this topic before (but I don't read all messages on this group).
> Applying such a transformation to my own code would be a mindless
> pattern matching job, and thus perfect for automation.
Indeed, and macros are the right tool for that. But if you want to
make such a macro general enough that it is safe to apply to
arbitrary code, expect to spend a lot of time on writing that macro.
It is easy enough to write a macro that goes through a form,
identifies + subforms, and rewrites them. However, not every form
beginning with + is a call to clojure.core/+. It might be a call to a
function called + in another namespace (for example
clojure.contrib.generic.arithmetic/+), or it might be part of a macro
template or some other quoted expression.
For these reasons, I'd suggest to apply your optimizations only to
code that you know well enough to be sure it doesn't contain some
tricks you might not have thought about.
Konrad.
> Konrad, I agree, it would make more sense to add simple optimisations
> like the one for addition to Clojure itself. However, the potential
> problem you mention regarding the clash of multiple definitions of `+`
> doesn't seem like that big an issue since an optimising macro can just
> call `resolve` against the first symbol and see if it is equal to
> `clojure.core/+`. Paul's suggestion also seems to get around this
> problem.
It's not a really big issue, I just wanted to illustrate with a
simple example that global code modifications requires a lot of
attention to details. As you say, checking if + resolves to
clojure.core/+ is one step in the correct procedure, but it's not
sufficient. You need to check first if + is not locally bound in a
surrounding let statement, or a surrounding function argument list.
And you must expand all surrounding macros first, because they might
transform the + into something else as well. In fact, the only
practical way to to the kind of optimization you envisage is to do a
full macro expansion first, and then walk through the code, keeping
track of let* an fn* forms.
I have done exactly that recently when writing clojure.contrib.macro-
utils, which contains a full macro expansion engine because that's
the only way to implement symbol macros and local macros. It was an
interesting exercice in macro programming.
Konrad.
> I have done exactly that recently when writing clojure.contrib.macro-
> utils, which contains a full macro expansion engine because that's
> the only way to implement symbol macros and local macros. It was an
> interesting exercice in macro programming.
It just occurred to me that local macros are probably a very good way
for implementing the kind of optimization you have in mind:
(macrolet [(fn + [& ops] ...)]
(expr1)
(expr2)
...)
takes care of not touching occurrences of + that should be left as
is, and correctly interacts with other macros. Inside the (fn + ...),
you just have to make sure that your output forms use clojure.core/+
instead of an unqualified +, to prevent the macro from being executed
in an endless loop. Using a local macro will even make sure that +
passed as a parameter to other functions is not touched. The only
limitation is that code using clojure.core/+ explicitly is not
modified. This may well be an advantage, as it provides an escape
mechanism from optimization.
Konrad.
Rich, would you accept a patch to make all arities inlinable for basic
math ops?
--
Professional: http://cgrand.net/ (fr)
On Clojure: http://clj-me.blogspot.com/ (en)
It would rewrite (+ 1 2 3) as (+ (+ 1 2) 3) but this implies to break
some cases, currently we have:
user=> (+ Integer/MAX_VALUE Integer/MAX_VALUE)
java.lang.ArithmeticException: integer overflow (NO_SOURCE_FILE:0)
user=> (+ Integer/MAX_VALUE Integer/MAX_VALUE Integer/MAX_VALUE)
6442450941
with such a patch, we would have:
user=> (+ Integer/MAX_VALUE Integer/MAX_VALUE Integer/MAX_VALUE)
java.lang.ArithmeticException: integer overflow (NO_SOURCE_FILE:0)
it's a change but at least (+ a b c) and (+ (+ a b) c) would behave
similarly.
(and to be more specific this patch would certainly introduce a private
macro: def-left-associative-op)
Christophe Grand a écrit :