学习Common Lisp(使用GNU CLISP 2.43)..所以可能是一个noob错误。例如'x和y'之间的'打印素数'
(defun is-prime (n)
(if (< n 2) (return-from is-prime NIL))
(do ((i 2 (1+ i)))
((= i n) T)
(if (= (mod n i) 0)
(return NIL))))
(defun next-prime-after (n)
(do ((i (1+ n) (1+ i)))
((is-prime i) i)))
(defmacro do-primes-v2 ((var start end) &body body)
`(do ((,var (if (is-prime ,start)
,start
(next-prime-after ,start))
(next-prime-after ,var)))
((> ,var ,end))
,@body))
(defmacro do-primes-v3 ((var start end) &body body)
(let ((loop-start (gensym))
(loop-end (gensym)))
`(do ((,loop-start ,start)
(,loop-end ,end)
(,var (if (is-prime ,loop-start)
,loop-start
(next-prime-after ,loop-start))
(next-prime-after ,var)))
((> ,var ,loop-end))
,@body )))
do-primes-v2完美无缺。
[13]> (do-primes-v2 (p 10 25) (format t "~d " p)) 11 13 17 19 23
接下来我尝试使用gensym来避免在宏扩展中命名冲突 - do-primes-v3。但是我坚持使用
*** - EVAL: variable #:G3498 has no value
尝试使用宏扩展来查看我是否能发现错误,但我不能。
[16]> (macroexpand-1 `(do-primes-v3 (p 10 25) (format t "~d " p))) (DO ((#:G3502 10) (#:G3503 25) (P (IF (IS-PRIME #:G3502) #:G3502 (NEXT-PRIME-AFTER #:G3502)) (NEXT-PRIME-AFTER P))) ((> P #:G3503)) (FORMAT T "~d " P)) ;
答案 0 :(得分:5)
使用DO*
代替DO
。
DO
在不但可见的范围内初始化绑定。 DO*
在 可见的范围内初始化绑定。
在这种特殊情况下,var
需要引用另一个绑定loop-start
。
答案 1 :(得分:4)
这里实际上并不需要gensym
来避免变量捕获,因为你没有引入任何“本地宏”的变量。当您宏观扩展do-primes-v2
时,您将看到没有引入在宏之外不存在的变量。
但是你确实需要它用于不同的事情:避免多重评估。
如果您像这样调用宏:
(do-primes-v2 (p (* x 2) (* y 3)) (format "~a~%" p))
它扩展到
(do ((p (if (is-prime (* x 2)) (* x 2) (next-prime-after (* x 2)) (next-prime-after p))) ((> p (* y 3)) (format "~a~%" p))
充其量,这是低效的,因为这些乘法是多次完成的。但是,如果您使用带有副作用的函数作为输入,例如setf
或incf
,这可能是一个大问题。
答案 2 :(得分:3)
将loop-start和loop-end的绑定移动到封闭的LET块或使用DO *。原因是DO中的所有循环变量都是“并行”绑定的,因此对于第一个绑定,(扩展的)循环开始变量还没有绑定。
答案 3 :(得分:1)
我知道这并没有真正回答你的问题,但我认为这是相关的。根据我的经验,您尝试编写的宏类型非常常见。我遇到问题的方法有一个问题就是它没有处理另一个常见的用例:功能组合。
我没有时间强调你可能会遇到的一些困难使用你的宏,但我会强调,如果你构建了面向功能组合的主要迭代器,你的宏结果非常简单,完全避免了你的问题。
注意:我稍微修改了一些功能。
(defun is-prime (n)
(cond
((< n 2)
nil)
((= n 2)
t)
((evenp n)
nil)
(t
(do ((i 2 (1+ i)))
((= i n) t)
(when (or (= (mod n i) 0))
(return nil))))))
(defun next-prime (n)
(do ((i n (1+ i)))
((is-prime i) i)))
(defun prime-iterator (start-at)
(let ((current start-at))
(lambda ()
(let ((next-prime (next-prime current)))
(setf current (1+ next-prime))
next-prime))))
(defun map-primes/iterator (fn iterator end)
(do ((i (funcall iterator) (funcall iterator)))
((>= i end) nil)
(funcall fn i)))
(defun map-primes (fn start end)
(let ((iterator (prime-iterator start)))
(map-primes/iterator fn iterator end)))
(defmacro do-primes ((var start end) &body body)
`(map-primes #'(lambda (,var)
,@body)
,start ,end))
我也建议您查看Series。生成器模式在lisp程序中也很常见。您可能还想查看Alexandria,特别是ALEXANDRIA:COMPOSE函数,看看您可以使用功能组合做些什么。
答案 4 :(得分:0)
我建议完全避免使用DO / DO *和宏,而是选择Series(可以在series.sourceforge.net找到其实现)。
如果这太复杂,那么考虑只生成一个带递归的素数列表或generator(用于按需生成)。