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)))
是否有必要担心函数参数的宏变量捕获(如#'equal)?如果
once-only
列表中没有这样的参数,或者如果#'equal
也是keyform的一部分,那么是否仍然存在潜在的冲突。 Paul Graham在他的着作On Lisp,第118页中说,一些可变捕获冲突导致“极其微妙的错误”,导致人们相信它可能更适合所有的事情。传递测试名称(如
equal
)而不是函数对象(如#'equal)是否更灵活?看起来你可以直接将名称放在函数调用位置(而不是使用funcall
),并允许使用宏和特殊形式以及函数?可能是
case-test
而不是是函数,而不是宏?
参见['fcase '](http://clisp.org/impnotes/fcase.html)在CLISP中。 – sds
亚历山大也有'SWITCH'为此。 – jkiiski
这与Lisp Machine Lisp中的SELECTOR类似。约1980. https://common-lisp.net/svn/mit-cadr/trunk/lisp/sys2/lmmac.lisp虽然SELECTOR不评估测试功能。 –