在 defmacro 中使用循环
Using loop inside defmacro
我正在学习(通用)Lisp,作为练习,我想实现 'xond',一个 cond
宏,它可以转换这个愚蠢的例子:
(xond (= n 1) (setq x 2) (= n 2) (setq x 1))
进入 if-else 链:
(if (= n 1) (setq x 2) (if (= n 2) (setq x 1)))
目前,我有这个宏:
(defmacro xond (&rest x) (if x (list 'progn (list 'if (pop x) (pop x)))))
只是展开 x
中的前两项:
(macroexpand '(xond (= x 1) (setq y 2)))
生产
(PROGN (IF (= X 1) (SETQ Y 2))) ;
现在我想处理 x
中的所有项目,所以我添加了一个 loop
来生成一个 if-serie(迈向 if-else-version 的一步):
(defmacro xond (&rest x)
(loop (if x
(list 'progn (list 'if (pop x) (pop x)))
(return t))))
但是宏似乎停止工作了:
(macroexpand '(xond (= x 1) (setq y 2)))
T ;
我在这里缺少什么?
版本
verdammelt 的回答让我走上了正确的轨道,coredump 的回答让我改变了我的迭代方法。
现在我将 (xond test1 exp1 test2 exp2)
实现为:
(block nil
test1 (return exp1)
test2 (return exp2)
)
可以通过迭代来完成。
我正在为我的最小 Lisp 解释器写这篇文章;我只实现了最基本的功能
这是我写的。我正在使用 la
来累积输出的各个部分。
(defmacro xond (&rest x)
(let ((la '()))
(loop
(if x (push (list 'if (pop x) (list 'return (pop x))) la)
(progn (push 'nil la)
(push 'block la)
(return la)
)))))
与
(macroexpand '(xond (= x 1) (setq y 2) (= X 2) (setq y 1)))
结果:
(BLOCK NIL
(IF (= X 2) (RETURN (SETQ Y 1)))
(IF (= X 1) (RETURN (SETQ Y 2)))
) ;
第二版
为block
添加一个标签并将return
更改为return-from
,以避免与其他return
内部参数发生冲突。还为 append
更改了 push
以生成与参数相同顺序的代码。
(defmacro xond (&rest x)
(let ((label (gensym)) (la '()) (condition nil) (expresion nil))
(setq la (append la (list 'block label)))
(loop
(if x
(setq la (append la (list
(list 'if (pop x) (list 'return-from label (pop x))))))
(return la)))))
所以
(macroexpand '(xond (= x 1) (setq y 2) (= X 2) (setq y 1)))
现在给
(BLOCK #:G3187 (IF (= X 1) (RETURN-FROM #:G3187 (SETQ Y 2))) (IF (= X 2) (RETURN-FROM #:G3187 (SETQ Y 1))))
您的 xond
宏以 (return t)
结尾,因此它的计算结果为 t
而不是您累积的 if
表达式。
您可以使用 loop
的 collect
子句来累积您希望 return 的代码。例如:(loop for x in '(1 2 3) collect (* 2 x))
将计算为 (2 4 6)
。
一些评论
- 当你只扩展成一个
if
时,你不需要 progn
pop
的使用可能会让 reader(以及程序员)感到困惑,因为它改变了一个地方,也许你想从一个不太强制的方法开始
此外,在那种情况下,我认为 loop
方法没有帮助,因为您需要将 after 之后的表达式嵌套在正文中 在 一个以前构建的表单中,即使它可以完成,但简单地使用递归函数或“递归”宏来完成它有点复杂。
在这里我解释这两种方法,从“递归”宏开始(这里引用是因为宏不调用自身,而是扩展为对自身的调用)。
宏扩展固定点
如果我必须实现 xond
,我会编写一个扩展到对 xond
的其他调用的宏,直到宏扩展达到基本情况,其中不再有 xond
:
(defmacro xond (&rest body)
(if (rest body)
(destructuring-bind (test if-action . rest) body
`(if ,test ,if-action (xond ,@rest)))
(first body)))
例如,这个表达式:
(xond (= n 1) (setq x 2) (= n 2) (setq x 1))
第一个宏展开为:
(if (= n 1)
(setq x 2)
(xond (= n 2) (setq x 1)))
并最终达到固定点:
(if (= n 1)
(setq x 2)
(if (= n 2)
(setq x 1)
nil))
注意,你不能在 xond
的定义中直接使用 xond
,宏 扩展 作为对 xond
,然后 Lisp 再次展开。如果你不小心,你可能会得到一个无限的宏展开,这就是为什么你需要一个基本情况,其中宏 not 展开为 xond
.
调用递归函数的宏
或者,您可以在宏中调用递归函数,并一次展开所有内部形式。
使用 LABELS
,您将 xond-expand
绑定到递归函数。这是一个实际的递归方法:
(labels ((xond-expand (body)
(if body
(list 'if
(pop body)
(pop body)
(xond-expand body))
nil)))
(xond-expand '((= n 1) (setq x 2) (= n 2) (setq x 1))))
; => (IF (= N 1)
; (SETQ X 2)
; (IF (= N 2)
; (SETQ X 1)
; NIL))
怎么样
(ql:quickload :alexandria)
(defun as-last (l1 l2)
`(,@l1 ,l2))
(defmacro xond (&rest args)
(reduce #'as-last
(loop for (condition . branch) in (alexandria:plist-alist args)
collect `(if ,condition ,branch))
:from-end t))
(macroexpand-1 '(xond c1 b1 c2 b2 c3 b3))
;; (IF C1 B1 (IF C2 B2 (IF C3 B3))) ;
;; T
alexandria
的 plist-alist
用于配对参数,
loop
中的内在解构用于提取条件和分支。
辅助函数 as-last
将列表堆叠在一起
(a b c) (d e f) => (a b c (d e f))
.
(reduce ... :from-end t)
右折叠收集到的 (if condition branch)
子句的序列,使用 #'as-last
.
将它们相互堆叠
没有任何依赖关系
('不过,alexandria
算作依赖吗?;) )
(defun pairs (l &key (acc '()) (fill-with-nil-p nil))
(cond ((null l) (nreverse acc))
((null (cdr l)) (pairs (cdr l)
:acc (cons (if fill-with-nil-p
(list (car l) nil)
l)
acc)
:fill-with-nil-p fill-with-nil-p))
(t (pairs (cdr (cdr l))
:acc (cons (list (car l) (cadr l)) acc)
:fill-with-nil-p fill-with-nil-p))))
(defun as-last (l1 l2)
`(,@l1 ,l2))
(defmacro xond (&rest args)
(reduce #'as-last
(loop for (condition branch) in (pairs args)
collect `(if ,condition ,branch))
:from-end t))
(macroexpand-1 '(xond c1 b1 c2 b2 c3 b3))
;; (IF C1 B1 (IF C2 B2 (IF C3 B3))) ;
;; T
辅助函数 pairs
由 (a b c d e f)
=> ((a b) (c d) (e f))
.
(:fill-with-nil-p
确定在奇数个列表元素的情况下,最后一个元素是 (last-el)
还是 (last-el nil)
- 在后一种情况下填充 nil
).
我正在学习(通用)Lisp,作为练习,我想实现 'xond',一个 cond
宏,它可以转换这个愚蠢的例子:
(xond (= n 1) (setq x 2) (= n 2) (setq x 1))
进入 if-else 链:
(if (= n 1) (setq x 2) (if (= n 2) (setq x 1)))
目前,我有这个宏:
(defmacro xond (&rest x) (if x (list 'progn (list 'if (pop x) (pop x)))))
只是展开 x
中的前两项:
(macroexpand '(xond (= x 1) (setq y 2)))
生产
(PROGN (IF (= X 1) (SETQ Y 2))) ;
现在我想处理 x
中的所有项目,所以我添加了一个 loop
来生成一个 if-serie(迈向 if-else-version 的一步):
(defmacro xond (&rest x)
(loop (if x
(list 'progn (list 'if (pop x) (pop x)))
(return t))))
但是宏似乎停止工作了:
(macroexpand '(xond (= x 1) (setq y 2)))
T ;
我在这里缺少什么?
版本
verdammelt 的回答让我走上了正确的轨道,coredump 的回答让我改变了我的迭代方法。
现在我将 (xond test1 exp1 test2 exp2)
实现为:
(block nil
test1 (return exp1)
test2 (return exp2)
)
可以通过迭代来完成。
我正在为我的最小 Lisp 解释器写这篇文章;我只实现了最基本的功能
这是我写的。我正在使用 la
来累积输出的各个部分。
(defmacro xond (&rest x)
(let ((la '()))
(loop
(if x (push (list 'if (pop x) (list 'return (pop x))) la)
(progn (push 'nil la)
(push 'block la)
(return la)
)))))
与
(macroexpand '(xond (= x 1) (setq y 2) (= X 2) (setq y 1)))
结果:
(BLOCK NIL
(IF (= X 2) (RETURN (SETQ Y 1)))
(IF (= X 1) (RETURN (SETQ Y 2)))
) ;
第二版
为block
添加一个标签并将return
更改为return-from
,以避免与其他return
内部参数发生冲突。还为 append
更改了 push
以生成与参数相同顺序的代码。
(defmacro xond (&rest x)
(let ((label (gensym)) (la '()) (condition nil) (expresion nil))
(setq la (append la (list 'block label)))
(loop
(if x
(setq la (append la (list
(list 'if (pop x) (list 'return-from label (pop x))))))
(return la)))))
所以
(macroexpand '(xond (= x 1) (setq y 2) (= X 2) (setq y 1)))
现在给
(BLOCK #:G3187 (IF (= X 1) (RETURN-FROM #:G3187 (SETQ Y 2))) (IF (= X 2) (RETURN-FROM #:G3187 (SETQ Y 1))))
您的 xond
宏以 (return t)
结尾,因此它的计算结果为 t
而不是您累积的 if
表达式。
您可以使用 loop
的 collect
子句来累积您希望 return 的代码。例如:(loop for x in '(1 2 3) collect (* 2 x))
将计算为 (2 4 6)
。
一些评论
- 当你只扩展成一个
if
时,你不需要 pop
的使用可能会让 reader(以及程序员)感到困惑,因为它改变了一个地方,也许你想从一个不太强制的方法开始
progn
此外,在那种情况下,我认为 loop
方法没有帮助,因为您需要将 after 之后的表达式嵌套在正文中 在 一个以前构建的表单中,即使它可以完成,但简单地使用递归函数或“递归”宏来完成它有点复杂。
在这里我解释这两种方法,从“递归”宏开始(这里引用是因为宏不调用自身,而是扩展为对自身的调用)。
宏扩展固定点
如果我必须实现 xond
,我会编写一个扩展到对 xond
的其他调用的宏,直到宏扩展达到基本情况,其中不再有 xond
:
(defmacro xond (&rest body)
(if (rest body)
(destructuring-bind (test if-action . rest) body
`(if ,test ,if-action (xond ,@rest)))
(first body)))
例如,这个表达式:
(xond (= n 1) (setq x 2) (= n 2) (setq x 1))
第一个宏展开为:
(if (= n 1)
(setq x 2)
(xond (= n 2) (setq x 1)))
并最终达到固定点:
(if (= n 1)
(setq x 2)
(if (= n 2)
(setq x 1)
nil))
注意,你不能在 xond
的定义中直接使用 xond
,宏 扩展 作为对 xond
,然后 Lisp 再次展开。如果你不小心,你可能会得到一个无限的宏展开,这就是为什么你需要一个基本情况,其中宏 not 展开为 xond
.
调用递归函数的宏
或者,您可以在宏中调用递归函数,并一次展开所有内部形式。
使用 LABELS
,您将 xond-expand
绑定到递归函数。这是一个实际的递归方法:
(labels ((xond-expand (body)
(if body
(list 'if
(pop body)
(pop body)
(xond-expand body))
nil)))
(xond-expand '((= n 1) (setq x 2) (= n 2) (setq x 1))))
; => (IF (= N 1)
; (SETQ X 2)
; (IF (= N 2)
; (SETQ X 1)
; NIL))
怎么样
(ql:quickload :alexandria)
(defun as-last (l1 l2)
`(,@l1 ,l2))
(defmacro xond (&rest args)
(reduce #'as-last
(loop for (condition . branch) in (alexandria:plist-alist args)
collect `(if ,condition ,branch))
:from-end t))
(macroexpand-1 '(xond c1 b1 c2 b2 c3 b3))
;; (IF C1 B1 (IF C2 B2 (IF C3 B3))) ;
;; T
alexandria
的 plist-alist
用于配对参数,
loop
中的内在解构用于提取条件和分支。
辅助函数 as-last
将列表堆叠在一起
(a b c) (d e f) => (a b c (d e f))
.
(reduce ... :from-end t)
右折叠收集到的 (if condition branch)
子句的序列,使用 #'as-last
.
没有任何依赖关系
('不过,alexandria
算作依赖吗?;) )
(defun pairs (l &key (acc '()) (fill-with-nil-p nil))
(cond ((null l) (nreverse acc))
((null (cdr l)) (pairs (cdr l)
:acc (cons (if fill-with-nil-p
(list (car l) nil)
l)
acc)
:fill-with-nil-p fill-with-nil-p))
(t (pairs (cdr (cdr l))
:acc (cons (list (car l) (cadr l)) acc)
:fill-with-nil-p fill-with-nil-p))))
(defun as-last (l1 l2)
`(,@l1 ,l2))
(defmacro xond (&rest args)
(reduce #'as-last
(loop for (condition branch) in (pairs args)
collect `(if ,condition ,branch))
:from-end t))
(macroexpand-1 '(xond c1 b1 c2 b2 c3 b3))
;; (IF C1 B1 (IF C2 B2 (IF C3 B3))) ;
;; T
辅助函数 pairs
由 (a b c d e f)
=> ((a b) (c d) (e f))
.
(:fill-with-nil-p
确定在奇数个列表元素的情况下,最后一个元素是 (last-el)
还是 (last-el nil)
- 在后一种情况下填充 nil
).