CLisp 10:用LISP的基本规则实现switch...case

来源:互联网 发布:java电商平台源码wap 编辑:程序博客网 时间:2024/05/20 06:56

Common LISP自带了case的实现,例如
(setq x 'b)
(case x) 返回nil
(case x
  (a (print "it is a"))
  ((b c) (print "it is b or c"))
  (otherwise (print "it is not a b and c")))
case的第一个参数是要判断的值,后面包含多个表达式,每个表达式是一个case分支。
case分支的第一个表达式可以是一个原子,或列表,或otherwise。

 

用基本规则cond实现case再合适不过了。
(case x)转换成(cond),两者都返回nil
第二个例子转换成下面语句,其中find是自带函数,先用着,后面讲它的实现
(cond ((eq x 'a) (print "it is a"))
      ((find x (quote (b c))) (print "it is b or c"))
      (t (print "it is not a b and c")))

 

LISP的宏是什么?说白了就是拼装LISP表达式的函数。大家可能在C++或Java中拼过SQL语句,或者拼过HTML网页,LISP的宏也就是这么回事。

实现case宏,就是让它能拼出上面例子中的表达式。因为case的分支数不是固定的,所以要用递归函数拼case的分支语句。

 

不管怎样,先写一个实现,再慢慢改问题
(defun case.branch (value body)
  (cond ((eq nil body) nil)
        (t (cons (cond ((eq (caar body) 'otherwise) `(t                                  ,@(cdar body)))
                       ((atom (caar body))          `((eq ,value (quote ,(caar body)))   ,@(cdar body)))
                       (t                           `((find ,value (quote ,(caar body))) ,@(cdar body))))
                 (case.branch value (cdr body))))))

(defmacro case. (value &rest body)
  (cond ((eq nil body) nil)
        (t `(cond ,@(case.branch value body)))))

 


对于这么复杂的宏,如何验证其正确性,出错时如何修改?函数macroexpand-1可以帮忙,该函数将宏展开,并打印出展开的结果。下面用它来

测试case.是否正确。
(macroexpand-1 '(case. x))
输出:NIL,这是((eq nil body) nil)所起的作用

(macroexpand-1 '(case .x
      (a (print 'a))
      ((b c d) (print 'bcd))))
输出:(COND ((EQ X 'A) (PRINT 'A))
            ((FIND X '(B C D)) (PRINT 'BCD)))

(macroexpand-1 '(case. (read-char)
      (a (print 'a))
      (b (print 'b))
      (otherwise (print 'other))))
输出:(COND ((EQ (READ-CHAR) 'A) (PRINT 'A))
            ((EQ (READ-CHAR) 'B) (PRINT 'B))
            (T (PRINT 'OTHER)))

 

最后一个例子出了点问题,本意是读一个字符,却会读很多字符,即每个分支读会读一个字符。
这是实现宏时的典型问题,叫“重复求值”,即输入的参数不是原子,而是一个表达式时,不应该重复对表达式求值。
解决办法时求一次值,将结果赋给一个临时变量,修改宏case.的实现
(defmacro case. (value &rest body)
  (let ((_value value))
    (cond ((eq nil body) nil)
          (t `(cond ,@(case.branch _value body)))))

 

上面的实现看起来是正确的,实际上犯大错了,没区分开“编译时计算”和“运行时计算”。编译时计算指展开宏时进行的计算,或者说拼语

句时的控制代码。对value的求值应该在运行时。
(defmacro case. (value &rest body)
  `(let ((_value ,value))
     ,(cond ((eq nil body) nil)
            (t `(cond ,@(case.branch '_value body))))))

 

上面实现解决了“重复求值”问题,又引入了“变量捕捉”问题。看下面的代码
(setq _value 'global-value)
(case. 'a (a (print _value)))
期望打印出global-value,实际打印出a,因为let中的局部变量 _value 屏蔽了全局变量 _value

解决此变量捕捉的办法是用gensym生成一个全局唯一的变量名,替代 _value
(defmacro case. (value &rest body)
 (let ((_value (gensym)))
  `(let ((,_value ,value))
     ,(cond ((eq nil body) nil)
            (t `(cond ,@(case.branch _value body)))))))

 

测试一下
(macroexpand-1 '(case. 'a (a (print _value))))
输出:(LET ((#:G3555 'A)) (COND ((EQ #:G3555 'A) (PRINT _VALUE))))

 

原创粉丝点击