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))))
- CLisp 10:用LISP的基本规则实现switch...case
- CLisp 7:用LISP的基本规则实现if
- CLisp 8:用LISP的基本规则实现while
- 用switch-case语句实现两个数的简单四则运算
- 使用map实现函数的switch-case
- switch-case的用法
- 汇编的switch case
- Switch case的应用
- switch...case...的用法
- switch case的使用
- switch case的定义
- python3的switch case
- 第一章 基本语法-10循开关语句(switch case)
- Python中switch-case实现
- python 实现switch/case语句
- C语言中switch case语句的实现
- C语言中switch case语句的实现
- [EXCEL]实现类似Switch case的函数功能
- 最近点对问题(分治思想的经典应用)
- centos 安装mysql
- 深入浅出FPGA-6-基于扫描的DFT对芯片测试的影响
- 【面试技巧】九大名企如何看简历
- dedecms后台管理员密码修改
- CLisp 10:用LISP的基本规则实现switch...case
- 我对 自旋锁 的理解
- 好久没看博客
- LINUX设备驱动之设备模型一--kobject
- mvc项目01_感受mvc的风景_jQuery validate验证_mvc+ajax
- 深入浅出FPGA-7-DFT之JTAG
- ios 音频底层
- HDU-1597(找规律)
- 【面试技巧】面试时不可不知的十大金科玉律