Lisp笔记
(首先介绍一些简单宏介绍)
一些简单的宏。
一:条件
1)IF
(if condition then-form [else-form])
(if (> 2 3) “Yup” “Nope”) -> Nope
PROGN可以按顺序执行任意数量的形式
(if (sam-p current-message)
(progn
(file-in-spam-folder current-message)
(update-spam-database current-message)))
可以替换成
WHEN:
(when (spam-p current-message)
(file-in-spam-folder current-message)
(update-spam-database current-message))
如果它没有内置到标准库中,也可以这样定义一个弘
(defmacro when (condition &rest body)
‘(if ,condition (progn ,@body)))
UNLESS:(与WHEN相反)
(defmacro when (condition &rest body)
‘(if (not ,condition) (progn ,@body)))
2)COND
(cond
Test-1 form*
.
.
Test-N form*))
3)AND,OR,NOT
(not nil) -> T
(not (= 1 1)) ->nil
(and (= 1 2) (= 3 3)) -> nil
(or (= 1 2) (= 3 3)) ->T
二:循环
1)DOLIST and DOTIMES
(dolist (x ‘(1 2 3)) (print x))->
1
2
Nil
(dotimes (i 3) (print i))
0
1
2
Nil
2)DO
(defun ((i 0 (+ 1 i))) i从0开始,每次加一
((>= i 4) (print i)) 如果i>=4,则退出并执行print i
(print i)) 不成立i>=4执行 print i == (dotimes (i 4) (print i))
3)LOOP
(do ((nums nil) (i 0 (+ i 0)))
((> i 10) (nreverse nums))
(push i nums))可以替换为
(loop for i from i to 10 collecting i) -> (1 2 3 4 5 6 7 8 9 10)
自定义宏:
有时是从想要编写的代码开始来编写宏的,就是说从一个示例的宏形式开始。其他时候则是在连续编写了相同的代码模式并认识到通过抽象该模式可以使代码更清晰后,才开始决定编写宏的。
其他简单宏:
1:truncate:
返回两个值,被截断的整数,以及原来数字的小数部分。
(truncate 26.21875) --> 26 0.21875
当调用只需要一个值时,被使用的就是第一个值:
(= (truncate 26.21875) 26) --> T
2:mapcar:
映射类函数,mapcar带有两个以上的参数,一个函数加上一个以上的列表(每个列表都分别是函数的参数),然后它可以将参数里的函数依次作用在每个列表的元素上。
(mapcar #’(lambda (x) (+ x 10)) ‘(1 2 3)) -->(11 12 13)
(mapcar #’+ ‘(1 2 3) ‘(10 100 1000)) --> 11 102 1003
3:psetq:(“parallel setq”)
(let ((a 1))
(setq a 2 b a)
(list a b)) -->(2 2)
用psetq就好像两个赋值并行执行一样
(let ((a 1))
(psetq a 2 b a)
(list a b))-->(2 1)
3:car cdr cons
(car (cons 1 2)) --> 1 (cdr (cons 1 2)) --> 2
(car (list 1 2 3 4)) --> 1 (cdr (list 1 2 3 4)) --> (2 3 4)
如何编写Lisp宏(详细讲解)
宏和常规函数工作方式截然不同,并且只有知道宏为何不同,以及怎样不同才是用好它们的关键。一个函数只产生结果,而宏却产生表达式,当它被求值时,才会产生结果。
1:简单宏
使用反引用:
(defmacro nif (expr pos zero neg)
‘(case (truncate (signum ,expr))
(1 ,pos)
(0 ,zero)
(-1 ,neg)))
不使用反引用:
(defmacro nif (expr pos zero neg)
(list ’case
(list ’truncate (list ’signum expr))
(list 1 pos)
(list 0 zero)
(list -1 neg)))
(mapcar #’(lambda (x) (nif x ’p ’z ’n)) ’(0 2.5 -8)) --> (Z P N)
以 (nif x ’p ’z ’n) 为例,从第一个定义中很容易就能看出来,这个表达式会展开成
(case (truncate (signum x))
(1 ’p)
(0 ’z)
(-1 ’n))
实现when:
(defmacro my-when (test &body body)
`(if ,test
(progn
,@body)))
2:测试宏展开:macroexpand和macroexpand-1
先实现一个while宏
(defmacro while (test &body body)
‘(do ()
((not ,test))
,@body))
(pprint (macroexpand ’(while (able) (laugh))))
(BLOCK NIL
(LET NIL
(TAGBODY
#:G61
(IF (NOT (ABLE)) (RETURN NIL))
(LAUGH)
(GO #:G61))))
T
(pprint (macroexpand-1 ’(while (able) (laugh))))
(DO NIL
((NOT (ABLE)))
(LAUGH))
T
写一个测试宏:
(defmacro mac (expr)
‘(pprint (macroexpand-1 ’,expr)))
执行(pprint (macroexpand-1 ’(or x y))) 就等价于 (mac (or x y))
3:宏的依赖关系
(defmacro mac (x)
‘(1+ ,x))
MAC
(setq fn (compile
nil ’(lambda (y) (mac y))))
#<Compiled-Function
BF7E7E>
> (defmacro mac (x)
‘(+ ,x 100))
MAC
> (funcall fn 1)
2
4:何时使用宏
实现while宏:
(defmacro while (test &body body)
‘(do ()
((not ,test))
,@body))
如果用函数实现,但body参数没办法处理。
(defmacro nil! (x) ‘(setf ,x nil))
只能用宏
Common Lisp解释宏:
首先编写一个求素数的宏:
(defun primep (number)
(when (> number 1)
(loop for fac from 2 to (isqrt number) never (zerop (mod number fac)))))
(defun next-prime (number)
(loop for n from number when (primep n) return n))
(defmacro do-primes ((var start end) &body body)
`(do ((,var (next-prime ,start) (next-prime (+ 1 ,var))))
((> ,var ,end))
,@body))
(do-primes (p 0 19) (format t "~d " p)) --> 2 3 5 6 11 13 17 19 NIL
重点:堵住漏洞(3个)
1:如果这样调用(do-primes (p 0 (random 100)) (format t “~d ” p)),end形式被求值超过一次,每次random都不同,所以可以这样,用一个中间变量保存
(defmacro do-primes ((var start end) &body body)
`(do ((ending-value ,end)
(,var (next-prime ,start) (next-prime (+ 1 ,var))))
((> , var ending-value))
,@body))
然而不幸的是,这一修复引入了两个新漏洞。
2:变量的初始形式式以变量被定义的顺序来求值的,当宏被展开求值时,传递给end的表达式在传递给start的表达式之前求值,这与它们出现在宏调用中的顺序相反。
(defmacro do-primes ((var start end) &body body)
`(do ((,var (next-prime ,start) (next-prime (+ 1 ,var)))
(ending-value ,end))
((> , var ending-value))
,@body))
3:最后一个需要堵上的漏洞时由于使用了变量名ending-value而产生的。
(do-primes (ending-value 0 10) (print ending-value)) 则是错误的。
(defmacro do-primes ((var start end) &body body)
(let ((ending-value-name (gensym)))
`(do ((,var (next-prime ,start) (next-prime (+ 1 ,var)))
(,ending-value-name ,end))
((> ,var ,ending-value-name))
,@body))
(do-primes (ending-value 0 10) (print ending-value))
展开生成下面的代码:
(do ((ending-value (next-prime 0) (next-prime (+ 1 ending-value)))
(#:g2141 10))
((> ending-value #:g2141))
(print ending-value))
现在用来保存循环终值的变量时生成符号,#:g2141,该符号名字G2141是由GENSYM所生成的,但这并不重要,重要的是这个符号的对象标识。生成符号时以未保留符号通常的语法形式打印出来的,带有前缀#:。
防止漏洞总结:
1)除非有特殊理由,否则需要将展开式中的任何子形式放在一个位置上,使其求值顺序与宏调用的子形式相同。
2)除非有特殊理由,否则需要确保子形式仅被求值一次,方法是在展开式中的创建变量来持有参数形式所得到的值,然后在展开式中所有需要用到该值的地方使用这个变量。
3)在宏展开期使用GENSYM来创建展开式中用到的变量名。