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)))
是否有必要担心功能参数的宏变量捕获(如#'等于)?这些论点是否可以不在once-only
列表中,或者如果#'equal
也是密钥形式的一部分,那么仍然可能存在潜在的冲突。保罗格雷厄姆在他的书On Lisp,第118页中说,一些变量捕捉冲突导致了极其微妙的错误,导致人们相信(gensym)
一切都可能更好。< / p>
传递测试名称(如equal
)而不是函数对象(如#&#39;等于)更灵活吗?看起来您可以直接将名称放在函数调用位置(而不是使用funcall
),并允许宏和特殊形式以及函数?
可以case-test
代替 一个函数而不是宏吗?
答案 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
转换为函数类似于将任何其他条件(if
和cond
)转换为函数 - 您将如何处理谚语
(case-test "a" #'string-equal
("A" (print "safe"))
("b" (launch missiles)))