选择/评估宏参数表单

时间:2017-04-10 15:13:38

标签: function macros case common-lisp

Common Lisp case宏始终默认为eql,用于测试其keyform是否与其子句中的某个键匹配。我使用以下宏进行瞄准以概括case以使用任何提供的比较函数(尽管使用已评估的键):

(defmacro case-test (form test &rest clauses)
  (once-only (form test)
    `(cond ,@(mapcar #'(lambda (clause)
                         `((funcall ,test ,form ,(car clause))
                           ,@(cdr clause)))
            `,clauses))))
使用

(defmacro once-only ((&rest names) &body body)
  "Ensures macro arguments only evaluate once and in order.
   Wrap around a backquoted macro expansion."
  (let ((gensyms (loop for nil in names collect (gensym))))
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
      `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
        ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
           ,@body)))))

例如:

(macroexpand '(case-test (list 3 4) #'equal
                ('(1 2) 'a 'b)
                ('(3 4) 'c 'd)))

给出

(LET ((#:G527 (LIST 3 4)) (#:G528 #'EQUAL))
  (COND ((FUNCALL #:G528 #:G527 '(1 2)) 'A 'B)
        ((FUNCALL #:G528 #:G527 '(3 4)) 'C 'D)))
  1. 是否有必要担心功能参数的宏变量捕获(如#&#39;等于)?这些论点是否可以不在once-only列表中,或者如果#'equal也是密钥形式的一部分,那么仍然可能存在潜在的冲突。保罗格雷厄姆在他的书On Lisp,第118页中说,一些变量捕捉冲突导致了极其微妙的错误,导致人们相信(gensym)一切都可能更好。< / p>

  2. 传递测试名称(如equal)而不是函数对象(如#&#39;等于)更灵活吗?看起来您可以直接将名称放在函数调用位置(而不是使用funcall),并允许宏和特殊形式以及函数?

  3. 可以case-test代替 一个函数而不是宏吗?

1 个答案:

答案 0 :(得分:2)

变量捕获

是的,您需要将该功能放入once-only,因为它可以动态创建。

极端的情况是:

(defun random-test ()
  (aref #(#'eq #'eql #'equal #'equalp) (random 4)))
(case-test foo (random-test)
  ...)

您希望确保整个test表单中的case-test相同。

名称与对象

评估test参数允许非常灵活的表单,如

(case-test foo (object-test foo)
  ...)

允许“面向对象”case-test

功能与宏

case-test转换为函数类似于将任何其他条件(ifcond)转换为函数 - 您将如何处理谚语

(case-test "a" #'string-equal
  ("A" (print "safe"))
  ("b" (launch missiles)))