【问题标题】:Passing a list to a macro correctly in Common Lisp在 Common Lisp 中正确地将列表传递给宏
【发布时间】:2017-01-24 12:05:23
【问题描述】:

我编写了一个宏来执行多个嵌套循环。我知道还有其他工具可以做到这一点,但我正在尝试学习如何编写宏,这似乎是一个很好的用例。它也有效(有点):

(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))。这是一个正确的假设吗?如果是这样,这是否是一般做法,即 - 使用传递给宏的文字列表的第二个值?

【问题讨论】:

    标签: macros common-lisp


    【解决方案1】:

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

    (dotimes-nested dims ...)
    

    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
    

    【讨论】:

      【解决方案2】:

      在您使用宏时,我发现您引用了文字列表,而在您的实现中,您实际上应用了 second 和注释 ; remove quote from the beginning of nlist

      宏接受代码输入并将宏函数应用于那些未计算的表达式,即纯粹是指表面语法的数据,结果是新代码。然后可以在使用它的每个地方使用宏替换此代码。

      扩展发生一次。通常,函数中的所有宏在存储函数时都会展开,并且在使用函数时,不会存在宏的痕迹。

      当你有类似的东西时:

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

      宏获取符号dims。然后,您的结果代码应该有 dims 以便在运行时最终评估为一个列表,因为您不知道宏扩展时它可能是什么。

      我会这样做:

      (dotimes* ((a 3) (b 2) (c 10))
        (format t "~A, ~A, ~A~%" a b c))
      
      (defmacro dotimes* ((&rest binding-initials) &body body)
        (loop :for (binding initial) :in (reverse binding-initials)
              :for result := `(dotimes (,binding ,initial nil) ,@body) 
                          :then `(dotimes (,binding ,initial nil) ,result)
              :finally (return result)))
      

      这样做的好处是您可以在此处使用变量:

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

      如果您有动态数量的变量,那么将其设为函数,就像@kiiski 的回答一样,会更好。

      【讨论】:

        猜你喜欢
        • 2019-11-03
        • 2013-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多