编写宏的宏 - 编译错误
Macros That Write Macros - Compile Error
当我编译以下代码时,SBCL 抱怨 g!-unit-value 和 g!-unit 未定义。我不确定如何调试它。据我所知,扁平化失败了。
当 flatten 到达 defunits 的未引用部分时,似乎整个部分都被视为一个原子。听起来正确吗?
以下使用书中的代码Let over Lambda:
保罗格雷厄姆公用事业公司
(defun symb (&rest args)
(values (intern (apply #'mkstr args))))
(defun mkstr (&rest args)
(with-output-to-string (s)
(dolist (a args) (princ a s))))
(defun group (source n)
(if (zerop n) (error "zero length"))
(labels ((rec (source acc)
(let ((rest (nthcdr n source)))
(if (consp rest)
(rec rest (cons (subseq source 0 n) acc))
(nreverse (cons source acc))))))
(if source (rec source nil) nil)))
(defun flatten (x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
放弃 Lambda 实用程序 - 第 3 章
(defmacro defmacro/g! (name args &rest body)
(let ((g!-symbols (remove-duplicates
(remove-if-not #'g!-symbol-p
(flatten body)))))
`(defmacro ,name ,args
(let ,(mapcar
(lambda (g!-symbol)
`(,g!-symbol (gensym ,(subseq
(symbol-name g!-symbol)
2))))
g!-symbols)
,@body))))
(defun g!-symbol-p (symbol-to-test)
(and (symbolp symbol-to-test)
(> (length (symbol-name symbol-to-test)) 2)
(string= (symbol-name symbol-to-test)
"G!"
:start1 0
:end1 2)))
(defmacro defmacro! (name args &rest body)
(let* ((o!-symbols (remove-if-not #'o!-symbol-p args))
(g!-symbols (mapcar #'o!-symbol-to-g!-symbol o!-symbols)))
`(defmacro/g! ,name ,args
`(let ,(mapcar #'list (list ,@g!-symbols) (list ,@o!-symbols))
,(progn ,@body)))))
(defun o!-symbol-p (symbol-to-test)
(and (symbolp symbol-to-test)
(> (length (symbol-name symbol-to-test)) 2)
(string= (symbol-name symbol-to-test)
"O!"
:start1 0
:end1 2)))
(defun o!-symbol-to-g!-symbol (o!-symbol)
(symb "G!" (subseq (symbol-name o!-symbol) 2)))
放弃 Lambda - 第 5 章
(defun defunits-chaining (u units prev)
(if (member u prev)
(error "~{ ~a~^ depends on~}"
(cons u prev)))
(let ((spec (find u units :key #'car)))
(if (null spec)
(error "Unknown unit ~a" u)
(let ((chain (second spec)))
(if (listp chain)
(* (car chain)
(defunits-chaining
(second chain)
units
(cons u prev)))
chain)))))
(defmacro! defunits (quantity base-unit &rest units)
`(defmacro ,(symb 'unit-of- quantity)
(,g!-unit-value ,g!-unit)
`(* ,,g!-unit-value
,(case ,g!-unit
((,base-unit) 1)
,@(mapcar (lambda (x)
`((,(car x))
,(defunits-chaining
(car x)
(cons
`(,base-unit 1)
(group units 2))
nil)))
(group units 2))))))
这有点棘手:
问题:您假设 backquote/comma 表达式是普通列表。
你需要问自己这个问题:
backquote/comma表达式的表示是什么?
是列表吗?
实际上未指定完整表示。看这里:CLHS: Section 2.4.6.1 Notes about Backquote
我们正在使用 SBCL。看到这个:
* (setf *print-pretty* nil)
NIL
* '`(a ,b)
(SB-INT:QUASIQUOTE (A #S(SB-IMPL::COMMA :EXPR B :KIND 0)))
所以逗号表达式由SB-IMPL::COMMA
类型的结构表示。 SBCL 开发人员认为,当需要通过漂亮的打印机打印此类反引号列表时,这种表示会有所帮助。
由于您的 flatten
将结构视为原子,因此它不会查看内部...
但这是SBCL的具体表现。 Clozure CL 做了其他事情,而 LispWorks 又做了其他事情。
Clozure CL:
? '`(a ,b)
(LIST* 'A (LIST B))
LispWorks:
CL-USER 87 > '`(a ,b)
(SYSTEM::BQ-LIST (QUOTE A) B)
调试
既然你发现不知何故 flatten
涉及,接下来的调试步骤是:
首先:跟踪函数 flatten
并查看调用了哪些数据以及调用了什么 returns。
由于我们不确定数据实际是什么,可以 INSPECT
它。
使用 SBCL 的调试示例:
* (defun flatten (x)
(inspect x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
STYLE-WARNING: redefining COMMON-LISP-USER::FLATTEN in DEFUN
FLATTEN
上面对参数数据调用 INSPECT
。在 Common Lisp 中,Inspector 通常是一种可以交互 inspect 数据结构的东西。
例如,我们使用反引号表达式调用 flatten
:
* (flatten '`(a ,b))
The object is a proper list of length 2.
0. 0: SB-INT:QUASIQUOTE
1. 1: (A ,B)
我们在交互式检查器中。现在可用的命令:
> help
help for INSPECT:
Q, E - Quit the inspector.
<integer> - Inspect the numbered slot.
R - Redisplay current inspected object.
U - Move upward/backward to previous inspected object.
?, H, Help - Show this help.
<other> - Evaluate the input as an expression.
Within the inspector, the special variable SB-EXT:*INSPECTED* is bound
to the current inspected object, so that it can be referred to in
evaluated expressions.
所以命令1
进入数据结构,这里是一个列表。
> 1
The object is a proper list of length 2.
0. 0: A
1. 1: ,B
再往里走:
> 1
The object is a STRUCTURE-OBJECT of type SB-IMPL::COMMA.
0. EXPR: B
1. KIND: 0
这里的Inspector告诉我们对象是某种类型的结构体。这就是我们想知道的。
我们现在使用命令 q
离开 Inspector 并且 flatten
函数继续并且 returns 一个值:
> q
(SB-INT:QUASIQUOTE A ,B)
致所有试图获得 defmacro 的人!在 SBCL 上工作,这个问题的一个临时解决方案是在展平过程中在 unquote 结构中摸索,递归地展平它的内容:
(defun flatten (x)
(labels ((flatten-recursively (x flattening-list)
(cond ((null x) flattening-list)
((eq (type-of x) 'SB-IMPL::COMMA) (flatten-recursively (sb-impl::comma-expr x) flattening-list))
((atom x) (cons x flattening-list))
(t (flatten-recursively (car x) (flatten-recursively (cdr x) flattening-list))))))
(flatten-recursively x nil)))
但这严重依赖于平台。如果我找到更好的方法,我会 post 它。
如果有人对此仍然感兴趣,这是我的三分钱。我对 flatten
的上述修改的反对意见是它可能更自然地像原来一样有用,而 unquote 表示的问题是 defmacro/g!
所特有的。我想出了一个不太漂亮的 defmacro/g!
修改,使用功能来决定要做什么。也就是说,在处理非 SBCL 实现 (#-sbcl
) 时,我们像以前一样进行,而在处理 SBCL (#+sbcl
) 的情况下,我们深入研究 sb-impl::comma
结构,使用它的 expr
属性并在 remove-duplicates
中使用 equalp
,因为我们现在处理的是结构,而不是符号。这是代码:
(defmacro defmacro/g! (name args &rest body)
(let ((syms (remove-duplicates
(remove-if-not #-sbcl #'g!-symbol-p
#+sbcl #'(lambda (s)
(and (sb-impl::comma-p s)
(g!-symbol-p (sb-impl::comma-expr s))))
(flatten body))
:test #-sbcl #'eql #+sbcl #'equalp)))
`(defmacro ,name ,args
(let ,(mapcar
(lambda (s)
`(#-sbcl ,s #+sbcl ,(sb-impl::comma-expr s)
(gensym ,(subseq
#-sbcl
(symbol-name s)
#+sbcl
(symbol-name (sb-impl::comma-expr s))
2))))
syms)
,@body))))
它适用于 SBCL。我还没有在其他实现上对其进行彻底测试。
当我编译以下代码时,SBCL 抱怨 g!-unit-value 和 g!-unit 未定义。我不确定如何调试它。据我所知,扁平化失败了。
当 flatten 到达 defunits 的未引用部分时,似乎整个部分都被视为一个原子。听起来正确吗?
以下使用书中的代码Let over Lambda:
保罗格雷厄姆公用事业公司
(defun symb (&rest args)
(values (intern (apply #'mkstr args))))
(defun mkstr (&rest args)
(with-output-to-string (s)
(dolist (a args) (princ a s))))
(defun group (source n)
(if (zerop n) (error "zero length"))
(labels ((rec (source acc)
(let ((rest (nthcdr n source)))
(if (consp rest)
(rec rest (cons (subseq source 0 n) acc))
(nreverse (cons source acc))))))
(if source (rec source nil) nil)))
(defun flatten (x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
放弃 Lambda 实用程序 - 第 3 章
(defmacro defmacro/g! (name args &rest body)
(let ((g!-symbols (remove-duplicates
(remove-if-not #'g!-symbol-p
(flatten body)))))
`(defmacro ,name ,args
(let ,(mapcar
(lambda (g!-symbol)
`(,g!-symbol (gensym ,(subseq
(symbol-name g!-symbol)
2))))
g!-symbols)
,@body))))
(defun g!-symbol-p (symbol-to-test)
(and (symbolp symbol-to-test)
(> (length (symbol-name symbol-to-test)) 2)
(string= (symbol-name symbol-to-test)
"G!"
:start1 0
:end1 2)))
(defmacro defmacro! (name args &rest body)
(let* ((o!-symbols (remove-if-not #'o!-symbol-p args))
(g!-symbols (mapcar #'o!-symbol-to-g!-symbol o!-symbols)))
`(defmacro/g! ,name ,args
`(let ,(mapcar #'list (list ,@g!-symbols) (list ,@o!-symbols))
,(progn ,@body)))))
(defun o!-symbol-p (symbol-to-test)
(and (symbolp symbol-to-test)
(> (length (symbol-name symbol-to-test)) 2)
(string= (symbol-name symbol-to-test)
"O!"
:start1 0
:end1 2)))
(defun o!-symbol-to-g!-symbol (o!-symbol)
(symb "G!" (subseq (symbol-name o!-symbol) 2)))
放弃 Lambda - 第 5 章
(defun defunits-chaining (u units prev)
(if (member u prev)
(error "~{ ~a~^ depends on~}"
(cons u prev)))
(let ((spec (find u units :key #'car)))
(if (null spec)
(error "Unknown unit ~a" u)
(let ((chain (second spec)))
(if (listp chain)
(* (car chain)
(defunits-chaining
(second chain)
units
(cons u prev)))
chain)))))
(defmacro! defunits (quantity base-unit &rest units)
`(defmacro ,(symb 'unit-of- quantity)
(,g!-unit-value ,g!-unit)
`(* ,,g!-unit-value
,(case ,g!-unit
((,base-unit) 1)
,@(mapcar (lambda (x)
`((,(car x))
,(defunits-chaining
(car x)
(cons
`(,base-unit 1)
(group units 2))
nil)))
(group units 2))))))
这有点棘手:
问题:您假设 backquote/comma 表达式是普通列表。
你需要问自己这个问题:
backquote/comma表达式的表示是什么?
是列表吗?
实际上未指定完整表示。看这里:CLHS: Section 2.4.6.1 Notes about Backquote
我们正在使用 SBCL。看到这个:
* (setf *print-pretty* nil)
NIL
* '`(a ,b)
(SB-INT:QUASIQUOTE (A #S(SB-IMPL::COMMA :EXPR B :KIND 0)))
所以逗号表达式由SB-IMPL::COMMA
类型的结构表示。 SBCL 开发人员认为,当需要通过漂亮的打印机打印此类反引号列表时,这种表示会有所帮助。
由于您的 flatten
将结构视为原子,因此它不会查看内部...
但这是SBCL的具体表现。 Clozure CL 做了其他事情,而 LispWorks 又做了其他事情。
Clozure CL:
? '`(a ,b)
(LIST* 'A (LIST B))
LispWorks:
CL-USER 87 > '`(a ,b)
(SYSTEM::BQ-LIST (QUOTE A) B)
调试
既然你发现不知何故 flatten
涉及,接下来的调试步骤是:
首先:跟踪函数 flatten
并查看调用了哪些数据以及调用了什么 returns。
由于我们不确定数据实际是什么,可以 INSPECT
它。
使用 SBCL 的调试示例:
* (defun flatten (x)
(inspect x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
STYLE-WARNING: redefining COMMON-LISP-USER::FLATTEN in DEFUN
FLATTEN
上面对参数数据调用 INSPECT
。在 Common Lisp 中,Inspector 通常是一种可以交互 inspect 数据结构的东西。
例如,我们使用反引号表达式调用 flatten
:
* (flatten '`(a ,b))
The object is a proper list of length 2.
0. 0: SB-INT:QUASIQUOTE
1. 1: (A ,B)
我们在交互式检查器中。现在可用的命令:
> help
help for INSPECT:
Q, E - Quit the inspector.
<integer> - Inspect the numbered slot.
R - Redisplay current inspected object.
U - Move upward/backward to previous inspected object.
?, H, Help - Show this help.
<other> - Evaluate the input as an expression.
Within the inspector, the special variable SB-EXT:*INSPECTED* is bound
to the current inspected object, so that it can be referred to in
evaluated expressions.
所以命令1
进入数据结构,这里是一个列表。
> 1
The object is a proper list of length 2.
0. 0: A
1. 1: ,B
再往里走:
> 1
The object is a STRUCTURE-OBJECT of type SB-IMPL::COMMA.
0. EXPR: B
1. KIND: 0
这里的Inspector告诉我们对象是某种类型的结构体。这就是我们想知道的。
我们现在使用命令 q
离开 Inspector 并且 flatten
函数继续并且 returns 一个值:
> q
(SB-INT:QUASIQUOTE A ,B)
致所有试图获得 defmacro 的人!在 SBCL 上工作,这个问题的一个临时解决方案是在展平过程中在 unquote 结构中摸索,递归地展平它的内容:
(defun flatten (x)
(labels ((flatten-recursively (x flattening-list)
(cond ((null x) flattening-list)
((eq (type-of x) 'SB-IMPL::COMMA) (flatten-recursively (sb-impl::comma-expr x) flattening-list))
((atom x) (cons x flattening-list))
(t (flatten-recursively (car x) (flatten-recursively (cdr x) flattening-list))))))
(flatten-recursively x nil)))
但这严重依赖于平台。如果我找到更好的方法,我会 post 它。
如果有人对此仍然感兴趣,这是我的三分钱。我对 flatten
的上述修改的反对意见是它可能更自然地像原来一样有用,而 unquote 表示的问题是 defmacro/g!
所特有的。我想出了一个不太漂亮的 defmacro/g!
修改,使用功能来决定要做什么。也就是说,在处理非 SBCL 实现 (#-sbcl
) 时,我们像以前一样进行,而在处理 SBCL (#+sbcl
) 的情况下,我们深入研究 sb-impl::comma
结构,使用它的 expr
属性并在 remove-duplicates
中使用 equalp
,因为我们现在处理的是结构,而不是符号。这是代码:
(defmacro defmacro/g! (name args &rest body)
(let ((syms (remove-duplicates
(remove-if-not #-sbcl #'g!-symbol-p
#+sbcl #'(lambda (s)
(and (sb-impl::comma-p s)
(g!-symbol-p (sb-impl::comma-expr s))))
(flatten body))
:test #-sbcl #'eql #+sbcl #'equalp)))
`(defmacro ,name ,args
(let ,(mapcar
(lambda (s)
`(#-sbcl ,s #+sbcl ,(sb-impl::comma-expr s)
(gensym ,(subseq
#-sbcl
(symbol-name s)
#+sbcl
(symbol-name (sb-impl::comma-expr s))
2))))
syms)
,@body))))
它适用于 SBCL。我还没有在其他实现上对其进行彻底测试。