Neither of these ideas rings true.
While there are things that fexprs can do that macros cannot, the reverse is also true.
Macros have the opportunity to work with the source code before it is executed. Macros can issue diagnostics. Also, macros have access to the compilation environment, which can be an entirely separate machine from the run-time environment. Macros can bring in materials from auxiliary files, which would not be found on the target.
A system build on fexprs rather than macros should incorporate the concept of static checking functions. For each fexpr, two additional functions should be required:
- a function which knows how to statically code-walk the form which is the target of the fexpr. Perhaps of this form:
(lambda (form recurse-fn)
;; function identifies, inside form, all expressions that are
;; forms and calls (recurse-fn subform) for each such subform
)
- a function which knows how to check the form for errors, so the programmer can be informed about misuses of the fexpr before it is actually called on the target system.Unhygienic macros do not have complicated semantics. However, it should be acknowledged that hygiene is more naturally achieved in fexprs without effort: the fexpr's own local variables and functions are very clearly separated from evaluations of argument material, that being done by an explicit eval call, using the correct environment passed into the fexpr. A macro's local variables are likewise clearly separated, but those are expansion time. A macro has to inject new variables into the generated code which a fexpr doesn't have to do: a fexpr can use its own local variables to hold run-time temporaries related to the calculation, whereas a macro cannot do that: for run-time temporaries, it has to generate code, combining that with the argument material. Those temporaries are then in the same scope, and have to be gensyms.