Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to get a macro to do an extra evaluation before returning its result?

I’m trying to get get my macro to do an extra evaluation of its result before returning it. Can this be done without eval?

I'm trying to solve the problem in exercise 4 below:

  1. Define a macro nth-expr that takes an integer n and an arbitrary number of expressions, evaluates the nth expression and returns its value. This exercise is easy to solve, if you assume that the first argument is a literal integer.

4. As exercise 3, but assume that the first argument is an expression to be evaluated.

It's easy to get the macro to pick the right expression:

(defmacro nth-expr% (n &rest es)
  `(nth ,n ',es))

CL-USER> (defvar i 1)
I
CL-USER> (nth-expr% (1+ i) (+ 2 3) (- 4 3) (+ 3 1))
(+ 3 1)

The expression (+ 3 1) is the one we want, but we want the macro to evaluate it to 4 before returning it.

It can of course be done with eval:

(defmacro nth-expr%% (n &rest es)
  `(eval (nth ,n ',es)))

CL-USER> (nth-expr%% (1+ i) (+ 2 3) (- 4 3) (+ 3 1))
4

But is there another way?

It feels like the solution should be to put the body of nth-expr% in a helper macro and have the top level macro only contain an unquoted call to this helper:

(defmacro helper (n es)
  `(nth ,n ',es))

(defmacro nth-expr (n &rest es) ; doesn't work!
  (helper n es))

The idea is that the call to helper would return (+ 3 1), and this would then be the expansion of the call to nth-expr, which at run-time would evaluate to 4. It blows up, of course, because N and ES get treated like literals.

like image 732
Knuto Avatar asked May 11 '19 15:05

Knuto


1 Answers

That's not that easy.

Using eval is not good, since eval does not evaluate the code in the local lexical environment.

Remember, if we allow an expression to be evaluated to determine the number of another expression to execute, then we don't know this number at macro expansion time - since the expression could be based on a value that needs to be computed - for example based on some variable:

(nth-expression
   foo
 (bar)
 (baz))

So we might want to think about code which does that:

(case foo
  (0 (bar))
  (1 (baz)))

CASE is evaluating foo and then uses the result to find a clause which has the same value in its head. The consequent forms of that clause then will be evaluated.

Now we need to write code which expands the former into the latter.

This would be a very simple version:

(defmacro nth-expression (n-form &body expressions)
  `(case ,n-form
     ,@(loop for e in expressions
             and i from 0
             collect `(,i ,e))))

Question: what might be drawbacks of using CASE like that?

like image 196
Rainer Joswig Avatar answered Sep 22 '22 17:09

Rainer Joswig