2017-04-10 72 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)))