I am a beginner lisp programmer, and I'm following the Practical Common Lisp book, specifically chapter 9.
After finishing the chapter, I've tried to expand the unit-testing environment. Specifically, I wanted a macro that gets a function name and a list of inputs and outputs instead of manually comparing them. i.e.
(check-i-o 1+ (2 3) (3 4)) => (check (= (1+ 2) 3) (= (1+ 3) 4))
I will admit that I am still a bit confused about the syntax of backtiks, and this probably is the problem, but this is the following code I wrote trying to program this
(defmacro check-i-o (function &body inputs-outputs)
`(check
,@(loop for i-o in inputs-outputs
collect `(= (,function (first i-o)) (second i-o)))))
For some reason, whenever I try to run this macro on an example (for instance, (check-i-o 1+ (2 3) (3 4))
) I encounter the error The variable I-O is unbound.
If it is relevant: I am using slime-repl sbcl in portacle emacs on windows.
Thank you so much for your help!
I have tried multiple variations on the code provided (mainly changing backticks, using #'1+ instead of 1+ when calling the macro, and trying to remove the &body from the decleration of the macro...) but nothing helped and I am at a loss...
You can macroexpand
your example code to debug your problem:
> (macroexpand '(check-i-o 1+ (2 3) (3 4)))
(CHECK
(= (1+ (FIRST I-O)) (SECOND I-O))
(= (1+ (FIRST I-O)) (SECOND I-O)))
The generated code contains references to I-O
, a variable that is not bound when you evaluate the expression. It was a variable bound inside your macro, but you directly inserted the I-O
symbol in the resulting expression. That's why you have an error.
The backquote/unquote mechanism is a way to produce a form where some parts are evaluated and other parts aren't: what is backquoted is not evaluated, except for the subterms which are unquoted.
In your code, you write:
`(= (,function (first i-o)) (second i-o)))
Only the content of the function
variable is inserted in the resulting form. The rest is left unevaluated. You need to write:
`(= (,function ,(first i-o)) ,(second i-o)))
Another improvement would be to use the fact that LOOP allows you to pattern-match (aka. destructure) lists for you:
,@(loop
for (i o) in inputs-outputs
collect `(= (,function ,i) ,o))))