1

I'm trying to make macros for defining various object similar to defparameter and defvar. The defregion1 macro works: upon executing it defines a variable with object region. However, defregion2 only returns an expression that must be executed manually. Here is the code:

(defclass location () ((x :initarg :x :accessor x) (y :initarg :y :accessor y))) (defclass region () ((x :initarg :x :accessor x) (y :initarg :y :accessor y) (w :initarg :w :accessor w) (h :initarg :h :accessor h))) (defmacro deflocation (var x y) `(defparameter ,var `(make-instance 'location :x ,x :y ,y))) (defmacro defregion1 (var x y w h) `(defparameter ,(intern (symbol-name var)) (make-instance 'region :x ,x :y ,y :w ,w :h ,h))) (defmacro defregion2 (var l1 l2) `(with-slots ((x1 x) (y1 y)) ,l1 (with-slots ((x2 x) (y2 y)) ,l2 `(defparameter ,(intern (symbol-name ,var)) (make-instance 'region :x ,x1 :y ,y1 :w (- ,x2 ,x1) :h (- ,y2 ,y1)))))) 

The output of defregion1:

(defregion1 *test-reg1* 1 2 3 4) => *test-reg1* 

The output of deferegion2:

(deflocation *l1* 20 30) (deflocation *l2* 50 60) (defregion2 '*test-reg2* *l1* *l2*) => (DEFPARAMETER *TEST-REG2* (MAKE-INSTANCE 'REGION :X 20 :Y 30 :W (- 50 20) :H (- 60 30))) 

I want *test-reg2* to also become a variable. What is wrong here?

1 Answer 1

2

You have two nested backquotes.

But your macro is also inside-out: you really want defparameter at the top-level, so something like this would be better:

(defmacro defregion2 (var l1 l2) `(defparameter ,(intern (symbol-name ,var)) (with-slots ((x1 x) (y1 y)) ,l1 (with-slots ((x2 x) (y2 y)) ,l2 (make-instance 'region :x x1 :y y1 :w (- x2 x1) :h (- y2 y1)))))) 

Also are you sure you want this slightly odd internery? What that's going to do is take the name of the symbol you give as an argument and intern it in the current package. So for instance

(defregion2 x:*foo* ...) 

will result in a symbol *foo* in the current package, instead of giving a value to x:*foo*. (Of course this all collapses into the same thing if the current package is x).

I suspect you possibly want

(defmacro defregion2 (var l1 l2) `(defparameter ,var (with-slots ((x1 x) (y1 y)) ,l1 (with-slots ((x2 x) (y2 y)) ,l2 (make-instance 'region :x x1 :y y1 :w (- x2 x1) :h (- y2 y1)))))) 

Your code is also potentially unhygenic as it binds variables (really symbol macros) with names which are visible to whatever l2 is: it would be safer as

(defmacro defregion2 (var l1 l2) `(defparameter ,var (let ((l1 ,l1) (l2 ,l2)) (with-slots ((x1 x) (y1 y)) l1 (with-slots ((x2 x) (y2 y)) l2 (make-instance 'region :x x1 :y y1 :w (- x2 x1) :h (- y2 y1))))))) 

This is now safe as you can see from the expansion:

(defregion2 *thing* (expression-involving x1 x2) (another-expression-involving x1 x2)) 

expands to

(defparameter *thing* (let ((l1 (expression-involving x1 x2)) (l2 (another-expression-involving x1 x2))) (with-slots ((x1 x) (x2 y)) l1 (with-slots ((x2 x) (y2 y)) l2 (make-instance 'region :x x1 :y y1 :w (- x2 x1) :h (- y2 y1)))))) 

You can see that the x1 in (another-expression-involving x1 ...) is not the one that is bound by with-slots.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. As for the intern, it kept evaluating to unbound variable so I tried to fix it. But this seems to be the problem of nested backquotes.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.