2017-04-10 73 views
1

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. 是否有必要担心函数参数的宏变量捕获(如#'equal)?如果once-only列表中没有这样的参数,或者如果#'equal也是keyform的一部分,那么是否仍然存在潜在的冲突。 Paul Graham在他的着作On Lisp,第118页中说,一些可变捕获冲突导致“极其微妙的错误”,导致人们相信它可能更适合所有的事情。

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

  3. 可能是case-test而不是函数,而不是宏?

+0

参见['fcase '](http://clisp.org/impnotes/fcase.html)在CLISP中。 – sds

+2

亚历山大也有'SWITCH'为此。 – jkiiski

+0

这与Lisp Machine Lisp中的SELECTOR类似。约1980. https://common-lisp.net/svn/mit-cadr/trunk/lisp/sys2/lmmac.lisp虽然SELECTOR不评估测试功能。 –

回答

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)))