普通的 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-band
或 value
不破坏扩展代码。
我正在处理一个复杂的宏并且 运行 遇到了障碍。
(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 的方法,所以我很困惑为什么我不能让它在这里工作
我已经测试了我创建的 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-band
或 value
不破坏扩展代码。