在您的代码中,宏在多个地方以受变量捕获 (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
绑定为零(您通常不希望这种行为)。
改写一个函数
与其为此编写一个大宏,不如先尝试了解您想要实现的目标,然后编写一个高阶函数,即。调用用户提供的函数的函数。
如果我理解正确,您的函数会按条目带迭代哈希表。我假设vars
拥有一个(key value)
符号对列表,例如((k1 v1) (k2 v2))
. 然后,body
对带中的所有键/值对起作用。
在下面的代码中,函数map-each-hashtable-band
接受一个函数,一个哈希表,而不是vars
它接受一个大小,带的宽度(对的数量)。
请注意,在您的代码中,您只有一个 loop,它使用哈希表迭代器构建一个带。但是,由于宏名为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)
注意。对哈希表的迭代以任意顺序发生,不能保证您会以任何特定类型的顺序看到条目,这取决于实现。
使用我当前版本的 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
仅宏的解决方案,以确保完整性
如果你只想有一个,单个宏,你可以通过在宏中内联上述函数的主体开始,你不需要再使用apply
,但是你需要在主体周围建立绑定,destructuring-bind
像你一样使用. 初稿将简单如下,但请注意,这不是一个适当的解决方案:
(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: 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-band
或value
不破坏展开代码的变量。