普通的 lisp 宏不调用函数

Common lisp macro not calling function

我正在处理一个复杂的宏并且 运行 遇到了障碍。

(defmacro for-each-hashtable-band (body vars on &optional counter name)
  `(block o
     (with-hash-table-iterator (next-entry ,on)
       (destructuring-bind
            ,(apply #'append vars)
            (let ((current-band (list ,@(mapcar #'not (apply #'append vars)))))
              (for (i 1 ,(length (apply #'append vars)) 2)
                (multiple-value-bind
                      (succ k v) (next-entry)
                  (if succ
                      (progn
                        (setf (nth i current-band) k)
                        (setf (nth (+ 1 i) current-band) v))
                      (return-from o nil))))
              current-band)
          ,@body))))

我收到“评估中止于#” 我不明白为什么 next-entry 似乎对我创建的宏不可见。

我已经尝试将其简化为一个可复制的小示例,但是我找不到没有我创建的宏的最小场景,除了这个场景之外,无论我尝试什么,下一个条目都是不可见的,我一直设法在我的其他示例中找到调用 next-entry 的方法,所以我很困惑为什么我不能让它在这里工作

我已经测试了我创建的 for 宏,它似乎在大多数情况下都能正常工作,但由于某种原因,它看不到这个下一个条目变量。我如何让它可见?

在您的代码中有多个地方,宏以符合 variable capture (pdf).

的方式生成绑定
(defmacro for-each-hashtable-band (body vars on &optional counter name)
  `(block o ;; VARIABLE CAPTURE
     (with-hash-table-iterator (next-entry ,on) ;; VARIABLE CAPTURE
       (destructuring-bind ,(apply #'append vars)
           (let ((current-band ;;; VARIABLE CAPTURE
                   (list ,@(mapcar #'not (apply #'append vars)))))
             (for
                 (i ;;; VARIABLE CAPTURE
                  1 ,(length (apply #'append vars)) 2)
                 (multiple-value-bind (succ k v) ;;; VARIABLE CAPTURE
                     ,(next-entry) ;;; WRONG EVALUATION TIME
                   (if succ
                       (progn
                         (setf (nth i current-band) k)
                         (setf (nth (+ 1 i) current-band) v))
                       (return-from o nil))))
             current-band)
         ,@body))))

此类捕获的简化示例是:

`(let ((x 0)) ,@body)

上面,引入了x变量,但是如果代码在x已经绑定的上下文中展开,那么body将无法引用那个以前的 x 绑定,并且总是会看到 x 绑定为零(您通常不希望出现这种行为)。

改写一个函数

与其为此编写一个大宏,不如让我们首先尝试了解您想要实现的目标,然后编写一个 higher-order 函数,即。调用 user-provided 函数的函数。

如果我没理解错的话,你的函数通过 bands 个条目迭代 hash-table。我假设 vars 包含 (key value) 对符号的列表,例如 ((k1 v1) (k2 v2))。然后,body 作用于波段中的所有 key/value 对。

在下面的代码中,函数 map-each-hashtable-band 接受一个函数,一个 hash-table,而不是 vars 它接受一个大小,带的宽度(对)。

请注意您的代码如何只有 一个循环 ,它使用 hash-table 迭代器构建一个带。但是,由于宏被命名为 for-each-hashtable-band,我假设您还想遍历所有波段。宏 with-hash-table-iterator 提供了一个迭代器但不循环自身。这就是为什么我在这里有两个循环。

(defun map-each-hashtable-band (function hash-table band-size)
  (with-hash-table-iterator (next-entry hash-table)
    (loop :named outer-loop :do
      (loop
        :with key and value and next-p
        :repeat band-size
        :do (multiple-value-setq (next-p key value) (next-entry))
        :while next-p
        :collect key into current-band
        :collect value into current-band
        :finally (progn
                   (when current-band
                     (apply function current-band))
                   (unless next-p
                     (return-from outer-loop)))))))

例如:

(map-each-hashtable-band (lambda (&rest band) (print `(:band ,band)))
                         (alexandria:plist-hash-table
                          '(:a 0 :b 1 :c 2 :d 3 :e 4 :f 5 :g 6))
                         2)

注意。迭代 hash-table 以任意顺序发生,不能保证您会以任何特定类型的顺序看到条目,这是 implementation-dependant.

使用我当前版本的 SBCL,打印如下:

(:BAND (:A 0 :B 1)) 
(:BAND (:C 2 :D 3)) 
(:BAND (:E 4 :F 5)) 
(:BAND (:G 6)) 

将函数包装在宏中

之前的函数可能不是您想要的行为,因此您需要根据自己的需要进行调整,但是一旦它完成了您想要的操作,您就可以围绕它包装一个宏。

(defmacro for-each-hashtable-band (vars hash-table &body body)
  `(map-each-hashtable-band (lambda ,(apply #'append vars) ,@body)
                            ,hash-table
                            ,(length vars)))

例如:

(let ((test (alexandria:plist-hash-table '(:a 0 :b 1 :c 2 :d 3 :e 4 :f 5))))
  (for-each-hashtable-band ((k1 v1) (k2 v2)) test
    (format t "~a -> ~a && ~a -> ~a ~%" k1 v1 k2 v2)))

这会打印:

A -> 0 && B -> 1 
C -> 2 && D -> 3 
E -> 4 && F -> 5 

Macro-only解决方案,为了完整性

如果你只想有一个单一的宏,你可以从宏中内联上述函数的body开始,你不需要再使用apply,但是相反,您需要像以前一样使用 destructuring-bind 围绕 body 建立绑定。第一稿将简单如下,但请注意,这不是一个合适的解决方案:

(defmacro for-each-hashtable-band (vars hash-table &body body)
  (let ((band-size (length vars)))
    `(with-hash-table-iterator (next-entry ,hash-table)
       (loop :named outer-loop :do
         (loop
           :with key and value and next-p
           :repeat ,band-size
           :do (multiple-value-setq (next-p key value) (next-entry))
           :while next-p
           :collect key into current-band
           :collect value into current-band
           :finally (progn
                      (when current-band
                        (destructuring-bind ,(apply #'append vars) current-band
                          ,@body))
                      (unless next-p
                        (return-from outer-loop))))))))

为了避免宏的变量捕获问题,您引入的每个临时变量都必须以一个符号命名,该符号不能存在于您扩展代码的任何上下文中。所以我们首先取消引用所有变量,使宏定义无法编译:

(defmacro for-each-hashtable-band (vars hash-table &body body)
  (let ((band-size (length vars)))
    `(with-hash-table-iterator (,next-entry ,hash-table)
       (loop :named ,outer-loop :do
         (loop
           :with ,key and ,value and ,next-p
           :repeat ,band-size
           :do (multiple-value-setq (,next-p ,key ,value) (,next-entry))
           :while ,next-p
           :collect ,key into ,current-band
           :collect ,value into ,current-band
           :finally (progn
                      (when ,current-band
                        (destructuring-bind ,(apply #'append vars) ,current-band
                          ,@body))
                      (unless ,next-p
                        (return-from ,outer-loop))))))))

编译宏时,宏应该将符号注入代码,但这里出现编译错误,显示 undefined variables:

;; undefined variables: CURRENT-BAND KEY NEXT-ENTRY NEXT-P OUTER-LOOP VALUE

所以现在,这些变量应该是新的符号:

(defmacro for-each-hashtable-band (vars hash-table &body body)
  (let ((band-size (length vars)))
    (let ((current-band (gensym))
          (key (gensym))
          (next-entry (gensym))
          (next-p (gensym))
          (outer-loop (gensym))
          (value (gensym)))
      `(with-hash-table-iterator (,next-entry ,hash-table)
         (loop :named ,outer-loop :do
           (loop
             :with ,key and ,value and ,next-p
             :repeat ,band-size
             :do (multiple-value-setq (,next-p ,key ,value) (,next-entry))
             :while ,next-p
             :collect ,key into ,current-band
             :collect ,value into ,current-band
             :finally (progn
                        (when ,current-band
                          (destructuring-bind ,(apply #'append vars) ,current-band
                            ,@body))
                        (unless ,next-p
                          (return-from ,outer-loop)))))))))

上面的内容有点冗长,但您可以将其简化。 这是之前的 for-each-hashtable-band 示例 使用此新宏扩展为 的内容:

(with-hash-table-iterator (#:g1576 test)
  (loop :named #:g1578
        :do (loop :with #:g1575
                  and #:g1579
                  and #:g1577
                  :repeat 2
                  :do (multiple-value-setq (#:g1577 #:g1575 #:g1579) (#:g1576))
                  :while #:g1577
                  :collect #:g1575 into #:g1574
                  :collect #:g1579 into #:g1574
                  :finally (progn
                            (when #:g1574
                              (destructuring-bind
                                  (k1 v1 k2 v2)
                                  #:g1574
                                (format t "~a -> ~a && ~a -> ~a ~%" k1 v1 k2
                                        v2)))
                            (unless #:g1577 (return-from #:g1578))))))

每次展开它时,#:gXXXX 变量都是不同的,并且不可能隐藏现有的绑定,因此例如,body 可以使用像 current-bandvalue 不破坏扩展代码。