将循环重写为mapcar

时间:2015-10-06 00:51:06

标签: macros lisp common-lisp

关注Practical Common Lisp,我们正在寻找一个简单的自动化单元测试框架。我们正在尝试编写一个宏来使用:

(check (= (+ 1 2) 3) (= (- 1 4) 9))

这应该扩展为使用先前定义的函数report-result的东西。建议的实施是:

(defmacro check (&body forms)
  `(progn
     ,@(loop for f in forms collect `(report-result ,f ',f))))

然而,这种扩展对我来说似乎是程序性的。我想用loop替换mapcar以扩展为类似的内容:

(mapcar #'(lambda (form) (report-result form 'form)) (list form-1 ... form-n))

但是,我显然缺乏宏观写作技巧。有人能想出一个这样的宏吗?

如果相关,则为report-result

的定义
(defun report-result (result form)
  (format t "~:[FAIL~;pass~] ... ~a~%" result form))

1 个答案:

答案 0 :(得分:3)

确实相当简单:您只需将collect表达式放入mapcar的正文中:

(defmacro check (&body forms)
  `(progn
    ,@(mapcar #'(lambda (form)
                  `(report-result ,form ',form))
              forms)))

你真的不需要知道关于"宏-y"为了进行你想要的替换,这就是你正在进行的替换,这只是用一些其他等价的表达式替换loop:它在宏上下文中也可以像在外面一样工作。 / p>

如果您希望扩展为mapcar,但没有真正的理由这样做,因为列表的大小在编译时是已知的。这是什么样子:

(defmacro check (&body forms)
  `(let ((results (list ,@(mapcar #'(lambda (form)
                                      `(list ,form ',form))
                                  forms))))
     (mapcar #'(lambda (result)
                 (report-result (car result) (cadr result)))
             results)))

扩展如此

> (macroexpand-1 '(check (+ 1 2) (* 2 3)))
(let ((results (list (list (+ 1 2) '(+ 1 2))
                     (list (* 2 3) '(* 2 3)))))
  (mapcar #'(lambda (result) (report-result (car result) (cadr result)))
          results))

你可以看到的是相当尴尬:宏已经有像(+ 1 2)这样的形式,但是为了将它们保存到运行时以便mapcar lambda看,你必须发出输入形式两次。而且你必须制作整个列表来映射,而不是仅仅生成一个列表已经完成"首先。此外,这会产生一个列表作为输出,并且需要同时在内存中包含所有输入和输出:原始宏progn一次生成一个输入和输出,并在完成后丢弃它们。