The fundamental reason is really the problem of how to implement macros.
Most Lisp's are targetted towards compilation. So what happens is really like this:
your code
|
v
macro expander
|
v
compiler
This means that if you define this code:
(mac my-add (x y)
`(+ ,x ,y))
(def my-function (x y z)
(my-add (my-add x y) z))
Then the macro expander will expand the code to
(set my-function
(fn (x y z) (+ (+ x y) z)))
But if we really, really wanted to have macros as first class, then how would the following code get compiled?
(def my-function (x y)
(my-oper x y))
(mac my-oper (x y)
`(+ ,x ,y))
(pr (my-function x y))
(mac my-oper (x y)
`(- ,x ,y))
(pr (my-function x y)) ;exactly the same call, completely different meaning
(mac my-oper (x y)
`(* ,x ,y))
(pr (my-function x y)) ;exactly the same call, completely different meaning
If we supported macros as first-class objects, then a "compiled" program would have to compile itself while running, because the macros might have changed between invocations of the macro. In such a case, you might as well have just stuck with the interpreted version.
The problem isn't intractible (you could do JIT compilation and check if the macro inside the variable is still the same to the older macro you used), but it's not easy either. And besides, most people don't find a need to redefine macros anyway. So most Lisps just punt: the rule is, the macro exists before the Lisp reads the code.
But couldn't you decide that any macro was only evaluated once (so the my-oper in my-function wouldn't change), but macros were searched for in the lexical namespace anyway? This would mean that any call with a lexical in the functional position would have to be checked for macro-expansions at runtime, of course, but it would be slightly more reasonable.
Yes, but again: compilation during runtime. Meaning (most likely) some sort of JIT. Wanna try to implement this? You could start by hacking this onto pg's arc-to-scheme implementation.
Okay. Be careful to still be able to properly handle environments, without actually turning it into an Arc interpreter.
pg's ArcN is really an Arc-to-Scheme compiler. And I dearly suspect that this was the main reason for not implementing first-class macros. Macros are intended to work on the code before the compiler does, so having true first-class macros is a little difficult.