【发布时间】:2021-04-03 22:35:24
【问题描述】:
我正在学习(普通)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 内部参数冲突。还将push 更改为append 以生成与参数相同的代码。
(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))))
【问题讨论】:
-
编辑引入了不同的扩展,它具有不同的语义,即。如果多个测试为真。也许你想添加一个返回子句?
-
@coredump。我编辑了我的问题以按照建议添加退货。
-
您的
(block nil ... (return ...))方法不卫生:考虑(loop ... (xond ... (return 3)) ...)。你绝对不需要在这样的宏中使用return/return-from,但如果你这样做了,请使用 gensym 作为块名称。 -
@tfb。刚刚用你的 cmets 编辑了我的问题。我想用'return'跳过剩余的条件。
-
如果您采用生成
(if c1 r1 (if c2 r2 ...))的明显方法(如coredump 的回答中所建议的那样),则不需要这样做。在像 Lisp 这样的表达式语言中,这比在各处显式控制转移的糟糕命令式的东西要自然得多。
标签: loops lisp common-lisp lisp-macros