On 2021-11-28, Axel Reichert <
ma...@axel-reichert.de> wrote:
> What would be the "best-practice" way to implement this macro?
One way would be:
> (defun frequently-run-defun (number)
> (case number
> (1 (load-time-value (expensive-defun (list 0 1 2))))
> (2 (load-time-value (expensive-defun (list 1 2 3))))
> (3 (load-time-value (expensive-defun (list 2 3 4))))))
When this code is compiled, the compiler arranges for the expensive
calculations to be run one time when the compiled code is loaded.
The load-time values then become de facto literal objects that
are just retrieved whenever the expression's value is needed.
There is some eval in there somewhere under the hood; we just hid
it.
If you want to write a macro which takes a piece of syntax as an
argument (the usual habit of a macro) and converts the value of
that syntax to a literal, you cannot avoid eval.
That evaluation is not taking place in the usual time that macros
normally arrange: the evaluation time of the generated code.
When you let things evaluate in their usual time, then eval is
invisible. When you need unusual evaluation, then unless there
is an existing gadget for doing that (e.g. load-time-value if
you want load-time evaluation), then you will end up coding an eval
somewhere.
In the TXR Lisp language, I provided a built-in operator called
macro-time. If you write (macro-time E), E gets evaluated in the
top-level environment, to obtain a value V, and then (quote V) is the
result. Thus, you don't have to use eval. For some reason, I designated
this as a special operator. It is actually implemented right inside the
macro expander as a special case, though it could have been a macro.
In any case, the implementation of macro-time cheerfully calls eval.
Macros themselves are based on eval. Think about it; the Lisp
interpreter or compiler is scanning your code to expand macros. It sees
(mac 1 2 3). What does it do? It invokes the mac macro expander
function. But that is evaluation! The mac function contains expressions:
expressions that give the default values of optional and keyword
parameters, and expressions that make up its body. These (at least in
an abstract sense) are evaluated by eval. The (mac 1 2 3) call is
effectively evaled, and replaced with the result.
You just don't see explicit uses of the eval functions if you stick
to invoking evaluation in all the situations that the language
designers designed for you.
"evil eval" really just refers to going off the beaten path,
according to someone's opionion of what that is.
E.g. I designed macro-time for you in a certain non-CL dialect; so if
you are in that dialect and use macro-time, you are not perpetrating any
use of eval.
If you're in a dialet that doesn't have macro time, you have to write
your own, which involves eval, and so then you are raising eyebrows.
However, until this point in the article, I have been lying about
something, and it's time to come clean.
There is a way of writing macro-time in Common Lisp without using eval.
You can disguise eval by stashing an expression into the body of a
macrolet (local macro), and the macrolet can be generated by a macro:
(defmacro macro-time (expr)
(let ((secret-mac (gensym)))
`(macrolet ((,secret-mac () `(quote ,,expr)))
(,secret-mac))))
This works because invocation of macros is evaluation (IOEMIE?)
What is this doing? When we make a call such as
(macro-time (read-contents-of-file-as-string "/etc/shadow"))
first the macro-time macro is invoked. It calls (gensym) to allocate a
unique symbol G and binds that to secret-mac. Then it generates this
code:
(macrolet ((G () `(quote ,(read-contents-of-file-as-string "/etc/shadow"))))
(G))
This code replaces the (macro-time ...) invocation. So now the expander
is processing a macrolet. It establishes the definition of the local
macro G and processes the (G) form which tells it to call that macro.
The macro is called and so the `(quote ...) backquote expression is
evaluated: THERE IS OUR HIDDEN EVAL.
Summary: we got an expression to evaluate at macro-expansion time by
inserting it into the body of a locally defined macro, and throwing the
invocation of that macro into the path of the macro-expander.
P.S. there is no funtion read-contents-of-file-as-string, but you
can easily write one.
--
TXR Programming Language:
http://nongnu.org/txr
Cygnal: Cygwin Native Application Library:
http://kylheku.com/cygnal