The Common Lisp case macro always defaults to eql for testing whether its keyform matches one of the keys in its clauses. I'm aiming with the following macro to generalize case to use any supplied comparison function (although with evaluated keys):
(defmacro case-test (form test &rest clauses)
(once-only (form test)
`(cond ,@(mapcar #'(lambda (clause)
`((funcall ,test ,form ,(car clause))
,@(cdr clause)))
`,clauses))))
using
(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)))))
For example:
(macroexpand '(case-test (list 3 4) #'equal
('(1 2) 'a 'b)
('(3 4) 'c 'd)))
gives
(LET ((#:G527 (LIST 3 4)) (#:G528 #'EQUAL))
(COND ((FUNCALL #:G528 #:G527 '(1 2)) 'A 'B)
((FUNCALL #:G528 #:G527 '(3 4)) 'C 'D)))
Is it necessary to worry about macro variable capture for a functional argument (like #'equal)? Can such arguments be left off the
once-onlylist, or could there still be a potential conflict if#'equalwere part of the keyform as well. Paul Graham in his book On Lisp, p.118, says some variable capture conflicts lead to "extremely subtle bugs", leading one to believe it might be better to(gensym)everything.Is it more flexible to pass in a test name (like
equal) instead of a function object (like #'equal)? It looks like you could then put the name directly in function call position (instead of usingfuncall), and allow macros and special forms as well as functions?Could
case-testinstead be a function, instead of a macro?
fcasein CLISP. - sdsSWITCHfor this. - jkiiskiSELECTORlike in Lisp Machine Lisp. ca. 1980. common-lisp.net/svn/mit-cadr/trunk/lisp/sys2/lmmac.lisp Though SELECTOR does not evaluate the test function. - Rainer Joswig