2016-09-16 2 views
4

Я написал макрос, чтобы сделать несколько вложенных циклов. Я понимаю, что для этого есть другие возможности, но я пытаюсь научиться писать макросы, и это казалось хорошим вариантом использования. Он тоже работает (вид):Правильный переход к макросу в Common Lisp

(defmacro dotimes-nested (nlist fn) 
    (let ((index-symbs nil) 
     (nlist (second nlist))) ;remove quote from the beginning of nlist 
    (labels 
     ((rec (nlist) 
      (cond ((null nlist) nil) 
        ((null (cdr nlist)) 
        (let ((g (gensym))) 
        (push g index-symbs) 
        `(dotimes (,g ,(car nlist) ,g) 
         (funcall ,fn ,@(reverse index-symbs))))) 
        (t (let ((h (gensym))) 
         (push h index-symbs) 
         `(dotimes (,h ,(car nlist) ,h) 
          ,(rec (cdr nlist)))))))) 
     (rec nlist)))) 

погонных:

(macroexpand-1 '(dotimes-nested '(2 3 5) 
       #'(lambda (x y z) (format t "~A, ~A, ~A~%" x y z)))) 

выходы:

(DOTIMES (#:G731 2 #:G731) 
    (DOTIMES (#:G732 3 #:G732) 
    (DOTIMES (#:G733 5 #:G733) 
     (FUNCALL #'(LAMBDA (X Y Z) (FORMAT T "~A, ~A, ~A~%" X Y Z)) #:G731 #:G732 
       #:G733)))) 

и вызов макроса так:

(dotimes-nested '(2 3 5) 
       #'(lambda (x y z) (format t "~A, ~A, ~A~%" x y z))) 

возвращается правильно, что Я ожидаю:

0, 0, 0 
0, 0, 1 
0, 0, 2 
0, 0, 3 
0, 0, 4 
0, 1, 0 
0, 1, 1 
0, 1, 2 
0, 1, 3 
0, 1, 4 
0, 2, 0 
0, 2, 1 
0, 2, 2 
0, 2, 3 
0, 2, 4 
1, 0, 0 
1, 0, 1 
1, 0, 2 
1, 0, 3 
1, 0, 4 
1, 1, 0 
1, 1, 1 
1, 1, 2 
1, 1, 3 
1, 1, 4 
1, 2, 0 
1, 2, 1 
1, 2, 2 
1, 2, 3 
1, 2, 4 
2 

Однако, если я называю это так:

(let ((dims '(3 4))) 
    (dotimes-nested dims 
       #'(lambda (x y) (format t "~A, ~A~%" x y)))) 

Я получаю сообщение об ошибке:

; in: LET ((DIMS '(3 4))) 
;  (DOTIMES-NESTED DIMS #'(LAMBDA (X Y) (FORMAT T "~A, ~A~%" X Y))) 
; 
; caught ERROR: 
; during macroexpansion of (DOTIMES-NESTED DIMS #'(LAMBDA # #)). Use 
; *BREAK-ON-SIGNALS* to intercept. 
; 
; The value DIMS is not of type LIST. 

;  (LET ((DIMS '(3 4))) 
;  (DOTIMES-NESTED DIMS #'(LAMBDA (X Y) (FORMAT T "~A, ~A~%" X Y)))) 
; 
; caught STYLE-WARNING: 
; The variable DIMS is defined but never used. 
; 
; compilation unit finished 
; caught 1 ERROR condition 
; caught 1 STYLE-WARNING condition 

Как передать в переменную, которая не интерпретируется как символ но как это ценность? Должен ли я оценивать его в макросе, используя? Но я не могу понять, как это сделать. Также как будут работать обе ситуации: a: вызов макроса с литералом '(3 4) и b: передача символа, связанного с списком' (3 4)? Мне нужны отдельные макросы для каждого?

И наконец, у меня есть вторичный вопрос. Когда я передаю литерал для nlist, я должен использовать (second nlist), чтобы получить значения списка. Я понимаю, это потому, что '(3 4) получает расширение до (quote (3 4)) перед отправкой на макрос. Это правильное предположение? И если да, то является ли эта общая практика, то есть использовать второе значение литерального списка, переданного макросу?

ответ

5

Макросы расширяются до того, как код скомпилирован или запущен. Однако переменная DIMS существует только при запуске кода. Аргументы, приведенные в макрос байт данные, поэтому, когда вы делаете

(dotimes-nested dims ...) 

DIMS не является ссылка на переменный (которая еще не существует), а просто символ.

Также нет необходимости цитировать список при вызове макроса, так как это буквальные данные. Поэтому вы должны просто называть его (dotimes-nested (2 3 4) ...) (и не нужно ничего удалять в макросе).

Если вам нужно использовать переменные runtime для измерений, вы должны использовать регулярную функцию вместо макроса. Что-то вроде:

(defun dotimes-nested (nlist function) 
    (labels ((rec (nlist args) 
      (if (endp nlist) 
       (apply function (reverse args)) 
       (destructuring-bind (first . rest) nlist 
        (dotimes (i first) 
        (rec rest (cons i args))))))) 
    (rec nlist nil))) 

CL-USER> (let ((dims '(3 4))) 
      (dotimes-nested dims 
          #'(lambda (x y) (format t "~A, ~A~%" x y)))) 
0, 0 
0, 1 
0, 2 
0, 3 
1, 0 
1, 1 
1, 2 
1, 3 
2, 0 
2, 1 
2, 2 
2, 3 
NIL 
3

В вашем использовании макросов I место, которое вы имели котировках буквального список и в вашей реализации вы на самом деле применить second с комментарием ; remove quote from the beginning of nlist

Макрос принимает ввод кода и применяется макрофункции для этих неоцененных выражений, то есть чисто данных, просто ссылающихся на поверхностный синтаксис, в результате чего получается новый код. Затем этот код можно заменить на использование макроса в каждом месте, где он используется.

Расширение происходит один раз. Обычно все макросы в функции расширяются, когда функция хранится, и при использовании функции нет следов макроса, когда-либо существовавшего.

Если у вас есть что-то вроде:

(dotimes-nested dims 
       #'(lambda (x y) (format t "~A, ~A~%" x y)))) 

Макрос получает символ dims. В результате получившийся код должен иметь dims, чтобы он в конечном итоге оценивал список во время выполнения, так как вы понятия не имеете, каково это в момент расширения макроса.

Я бы сделал это, как это вместо:

Хорошая вещь о том, что вы можете использовать переменные здесь:

(defparameter *x* 10) 
(defparameter *y* 10) 
(dotimes* ((x *x*) (y *y*)) 
    (format t "(~a,~a)~%" x y)) 

Если у вас динамический ряд переменных, то делает это функция, как ответ @ kiiski, будет лучшим совпадением.