1

我正在学习(普通)Lisp,作为练习,我想实现一个cond宏“xond”,它可以改变这个愚蠢的例子:

(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并更改returnreturn-from,以避免与其他return内部参数冲突。还更改pushappend以与参数相同的顺序生成代码。

(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))))
4

3 回答 3

3

一些备注

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

另外,在这种情况下,我认为一种loop方法没有帮助,因为您需要将后面的表达式嵌套在先前构建的表单中,即使可以做到,但做起来有点复杂那只是一个递归函数或“递归”宏。

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

宏扩展固定点

如果我必须实现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))
于 2020-12-26T16:46:33.013 回答
2

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

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

于 2020-12-26T14:38:50.460 回答
0

怎么样

(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'splist-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)。

于 2020-12-31T10:08:35.657 回答