Reputation: 11
I am new to the common lisp language. I'm just 3 months into it. One day, it occurred to me that since it's the quote that prevents an s-expression from being eval'd, maybe I can write my own eval by 'dislodging' that "quote". Given the fact that a macro doesn't eval it's input, I write this:
(defmacro my-eval (x)
(car (cdr x)))
But it doesn't work! For example, when I write (my-eval '(car '(1 2 3))
I believe that this macro receives (quote (car (quote 1 2 3)))
as its input.
However it turns it into (car (quote (1 2 3))
, and then evals it, which value is 1, but actually sbcl1.1.15 print (quote (1 2 3))
. Can you explain to me what happened?
Upvotes: 0
Views: 136
Reputation: 85813
First, let's define the macro, look at the macroexpansion, and look at the result of evaluating a form using this macro. Then we figure out why each form produces what it does. The definition is easy; you've already provided that:
CL-USER> (defmacro my-eval (x)
(car (cdr x)))
MY-EVAL
Now let's take a look at what the macroexpansion of (my-eval '(car '(1 2 3)))
is:
CL-USER> (macroexpand-1 '(my-eval '(car '(1 2 3))))
(CAR '(1 2 3))
T
This is what we should expect. The form '(car '(1 2 3))
is short for
(quote (car (quote (1 2 3))))
The cdr
of that is
((car (quote (1 2 3))))
The car
of that is
(car (quote (1 2 3)))
That's the code that that (my-eval '(car '(1 2 3)))
will be replaced by. That means that when we evaluate (my-eval '(car '(1 2 3)))
, we should expect to see the same results as if we had evaluated that directly. Of course, (car '(1 2 3))
evaluates to 1
. Let's check:
CL-USER> (my-eval '(car '(1 2 3)))
1
quote
is a special operator in Common Lisp. It has the special behavior that (quote object)
returns object
. object
doesn't get evaluated. If it's a list, then you get a list back; if it's a number you get a number back, etc. But where does the object come from? In most cases, you're reading Lisp code form a file, or from a stream, and it's the reader's responsibility for creating the object from the textual representation. E.g., when you write '53
, or (quote 53)
, you get a number because the reader already produces the number 53 from you and returns the form that could be produced by (list 'quote 53)
(or (list 'quote '53)
). When the evaluator receives that form, it recognizes that it's a list and that the car
of that list is the symbol quote
, and so invokes the special operator named by quote
with the argument that is the second object of the list. That's a special behavior coded into the system.
Macros, on the other hand, allow you transform some forms, after they've been read by the reader, into new forms that can be passed to the evaluator. If you're trying to implement your own quote
-like form as macro, what would it need to do? It would need to turn
(kwote object)
into a form that would be guaranteed to evaluate to object
. You can't simply expand to object
, because you don't want to pass the object to the evaluator. If it's something that can be treated as another form, the evaluator will try to do more evaluation on it. The only thing that you can pass to the evaluator to be guaranteed to get object
back is a list of the
(quote object)
This means that if you want to implement your own quote, you'll have to do it terms of te built-in quote. E.g.,
CL-USER> (defmacro kwote (object)
(list 'quote object))
KWOTE
CL-USER> (kwote (1 2 3))
(1 2 3)
CL-USER> (kwote (car '(1 2 3)))
(CAR '(1 2 3))
CL-USER> (kwote '(car '(1 2 3)))
'(CAR '(1 2 3))
Upvotes: 0
Reputation: 48745
It's a mystery how you got (quote (1 2 3))
in SBCL.
Yes, you can remove quoting if the argument is indeed '(car '(1 2 3))
but not if your argument is a variable that contains the same evaluate quoted form. Eg.
(my-eval '(car '(1 2 3))) ; ==> 1
(setf test '(car '(1 2 3))) ; ==> (car '(1 2 3))
(my-eval test) ; ==> FAIL!
Why it won't work is because my-eval
will receive the symbol test
as the argument and not the value behind the symbol. Doing (cadr x)
is like doing (cadr 'test)
and it will fail. Macros is run before the variables have value (in macro-expansion time) so they can help reduce code size where you have lots of boilerplate but it cannot replace eval
.
Upvotes: 1
Reputation: 3231
OK so I cant see anything explicitly wrong with the macro except it's name. Really you are unquoting, not evaling. You are not executing the code here (which is fine) you are just removing the first quote.
I might suggest that you use this slight variation of your macro
(defmacro unquote (form)
(if (eq (first form) 'quote)
(second form)
(error "Cannot unquote this form as it is not quoted: ~s" form)))
Just for the sake of a little extra safety :)
Now back to expected results. Given:
(my-eval '(car '(1 2 3))
which, as you know is actually
(my-eval (quote (car (quote (1 2 3)))))
You are taking the cdr
of (quote (car (quote (1 2 3))))
which is
((car (quote (1 2 3))))
You are then taking the cdr
of that (which is correct) and that gives you
(car (quote (1 2 3)))
So far so good. Now because you are using a macro the result of the macro is inserted back into the code and then at run time the code will be executed, giving you the correct result of 1
.
I am going to suggest that the last issue you described (with sbcl 1.1.15) may actually be user error as I have tested this with an older version at it is working fine. Also if there was a bug there it is the kind of bug that would show itself very quickly in people's code :)
So the TLDR is there isn't anything wrong with your macro although it could do with some extra safety checks and a better name. The main thing to keep in mind that the macros are eval'ing at macroexpansion time but they are not evaluating their arguments (unless you force them to). Macros simply return code that will replace the macro form and be evaluated at run time.
Good luck with your lisping!
Upvotes: 1