【问题标题】:Using loop inside defmacro在 defmacro 中使用循环
【发布时间】: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


【解决方案1】:

一些意见

  • 当您仅扩展为单个 if 时,您不需要 progn
  • pop 的使用可能会让读者(以及程序员)感到困惑,因为它会改变一个地方,也许您想从一种不太重要的方法开始

另外,在这种情况下,我认为loop 方法没有帮助,因为您需要将 after 的表达式嵌套在主体 inside 中以前构建的形式,即使它可以完成,但简单地使用递归函数或“递归”宏来完成它有点复杂。

在这里我解释了这两种方法,从“递归”宏开始(这里的引用是因为宏不调用自身,而是扩展为对自身的调用)。

宏扩展固定点

如果我必须实现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 会再次扩展它。如果你不小心,你可能会得到一个无限的宏扩展,这就是为什么你需要一个基本情况,宏确实扩展成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))

【讨论】:

  • 根据xond 的用途,我认为可能需要一个 arglist 长度为 1 的错误情况(它应该是 0 或 2 或更多)。
  • 宏解构绑定中的 @tfb 会出错,但我同意应该有更好的错误处理。在标签中我也将重写以使用 pop,这样它也会出错。
  • @tfb 我不希望在答案中进行错误处理。可以稍后添加。
【解决方案2】:

您的xond 宏以(return t) 结尾,因此它的计算结果为t,而不是您累积的if 表达式。

您可以使用loopcollect 子句来累积您希望返回的代码。例如:(loop for x in '(1 2 3) collect (* 2 x)) 的计算结果为 (2 4 6)

【讨论】:

  • 我现在明白了。我理解这个问题;在 Python 中,我会在列表中累积字符串并返回 join 结尾,但在这里,我迷路了。代码将不胜感激。
  • 如果我有时间花在这个问题上,我会看看能不能提供一些东西来帮助你。现在我添加了一个关于loopcollect 子句的建议,这可能在这里有用。
  • 您的回答给出了解决问题的提示。我正在学习 Lisp,想用最基本的工具解决它。
【解决方案3】:

怎么样

(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

alexandriaplist-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)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-06
    • 2015-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多