在Common Lisp中正确地将列表传递给宏

ˆᵛˆ*_*ˆᵛˆ 4 macros 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))))
Run Code Online (Sandbox Code Playgroud)

运行:

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

输出:

(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))))
Run Code Online (Sandbox Code Playgroud)

并像这样调用宏:

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

正确返回我的期望:

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
Run Code Online (Sandbox Code Playgroud)

但是,如果我这样称呼它:

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

我收到一个错误:

; 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
Run Code Online (Sandbox Code Playgroud)

如何传入一个不被解释为符号但是它的值的变量?我应该使用宏来评估它吗?但我无法弄清楚如何.这两种情况如何运作,a:用文字'(3 4)调用宏,b:传入绑定到列表的符号'(3 4)?我是否需要单独的宏?

最后,我有一个次要问题.当我传入nlist的文字时,我必须(second nlist)用来获取列表值.我理解这是因为在发送到宏之前'(3 4)扩展(quote (3 4))到了.这是正确的假设吗?如果是这样,这是一般的做法,即 - 使用传递给宏的文字列表的第二个值?

jki*_*ski 5

在编译或运行代码之前扩展宏.DIMS但是,该变量仅在代码运行时存在.给宏的参数是文字数据,所以当你这样做时

(dotimes-nested dims ...)
Run Code Online (Sandbox Code Playgroud)

DIMS 不是对变量的引用(它还不存在),而只是一个符号.

调用宏时也不必引用列表,因为它仍然是文字数据.所以你应该调用它(dotimes-nested (2 3 4) ...)(并且不需要删除宏中的任何内容).

如果确实需要能够为维度使用(运行时)变量,则应使用常规函数而不是宏.就像是:

(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
Run Code Online (Sandbox Code Playgroud)