I believe you misunderstand me. I'm not talking about preprocessor macros, but TRUE compiler macros ala Lisp or Clojure, which can bring the full expressiveness of the language to bear on the task at hand, and generate program code as a return value.
Consider the following example, from Clojure, which implements a C-style for-loop
(defmacro for-loop [[sym init check change :as params] & steps]
(cond
(not (vector? params))
(throw (Error. "Binding form must be a vector for for-loop"))
(not= 4 (count params))
(throw (Error. "Binding form must have exactly 4 arguments in for-loop"))
:default
`(loop [~sym ~init value# nil]
(if ~check
(let [new-value# (do ~@steps)]
(recur ~change new-value#))
value#))))
Syntax primer: The code runs at compile time, forms (nested parenthized groups) that are prefixed with ` are the output, ~var means to substitute the _value_ var from the macro invocation, and var# means create a new anonymous variable var, guaranteed not to clobber or shadow any existing variable.
For instance, given out macro defined above, say we make the following call:
(for-loop [i 0, (< i 10), (inc i)] (println i))
The code generated by the compiler for that invocation is:
(loop* [i 0 value__95__auto__ nil]
(if (< i 10)
(clojure.core/let [new-value__96__auto__ (do (println i))]
(recur (inc i) new-value__96__auto__))
value__95__auto__))
You can see how
A: all the error checking is done at compile time and comes at no runtime cost
B: how the `-quoted code in the original macro gets "filled in".
C: How the arguments to the macro are not evaluated by the macro call or the substition until they are actually used.
Point C is quite powerful since it means that macros can be used to create arbitrary control structures that are fully as powerful and general as anything in the core or stdlib - and in fact many of those are implemented as macros.
I thought I made it clear. The macro system is not some subset, it is the full language with all the capabilities that brings with it, including type functions, reflection, etc. You can even do I/O in macros, whatever you want. The full language is available.
Forgive me. The term 'hygenic macro' has historically been associated with Lisp and Scheme which were not known for the availability of type information at compile time, because they are dynamically typed and often did not have a compile time.
Fair enough. Clojure really is a wonderful language. It's sort of a "Javascript: The Good Parts" for Lisp - they kept (and expanded on) that wonderful expressiveness, while doing away with 50 years of dogma that held on to stuff like gen-syms, car, cdr, and singly-linked lists as the one-true-datatype.
It's really not so bad. At least for simple-medium sized projects you can basically ignore the JVM. The leiningen build tool means you never have to tough maven/ivy/ant/etc