【问题标题】:What is wrong with the following Common Lisp macro using gensym?以下使用 gensym 的 Common Lisp 宏有什么问题?
【发布时间】:2010-10-07 21:15:41
【问题描述】:

学习 Common Lisp(使用 GNU CLISP 2.43).. 所以可能是一个菜鸟错误。例子是'打印 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) (格式 t "~d " p)) 11 13 17 19 23

接下来我尝试使用 gensym 来避免宏扩展中的命名冲突 - do-primes-v3。但是我被困住了

*** - EVAL:变量 #:G3498 没有值

尝试使用宏扩展来查看我是否可以发现错误,但我不能。

[16]> (macroexpand-1 `(do-primes-v3 (p 10 25) (格式 t "~d " p))) (做 ((#:G3502 10) (#:G3503 25) (P (IF (IS-PRIME #:G3502) #:G3502 (NEXT-PRIME-AFTER #:G3502)) (P)))) ((> P #:G3503)) (格式 T "~d " P)) ;

【问题讨论】:

  • Lisp 似乎弄乱了 stackoverflow 的语法着色。
  • 它是这样做的...尝试在行尾添加一个带有终止引号的注释以最大程度地减少影响
  • Gishu,反之亦然。使用 HTML pre 标签而不是 markdown 缩进来禁用语法高亮。
  • 我发现这段代码中空格的使用令人困惑。我可以把它整理得更接近标准格式吗?
  • 继续...我被引导相信这是 LISP 方式 :)

标签: lisp common-lisp


【解决方案1】:

使用DO* 代替DO

DO 在它们可见的范围内初始化绑定。 DO* 在它们可见可见的范围内初始化绑定。

在这种特殊情况下,var 需要引用另一个绑定 loop-start

【讨论】:

  • 在钱上.. 不知道 DO*。但是确实阅读了有关 LET 和 LET*.. 所以可以建立连接。谢谢!
  • 解决方案:问题是因为第三个变量的初始化形式需要第一个变量,这在 DO 中是​​不可能的(尽管步骤形式可以做到这一点,因为它们在每次迭代结束时进行评估) .要访问变量列表中之前在初始化表单中定义的变量,请使用 DO*
【解决方案2】:

你实际上不需要gensym 来避免变量捕获,因为你没有引入任何“宏本地”的变量。当您对 do-primes-v2 进行宏扩展时,您会看到没有引入宏之外不存在的变量。

不过,您确实需要它来做不同的事情:避免多重评估。

如果你这样调用宏:

(do-primes-v2 (p (* x 2) (* y 3)) (格式“~a~%”p))

它扩展到

(do ((p (if (is-prime (* x 2))) (* x 2) (下一个素数(* x 2)) (p)))) ((> p (* y 3)) (格式“~a~%”p))

这充其量是低效的,因为这些乘法需要多次执行。但是,如果您使用带有副作用的函数作为输入,例如 setfincf,这可能是一个大问题。

【讨论】:

  • 我是实用通用 Lisp 的 1/3。所以正在阅读有关宏设计的常见陷阱的信息。其中包括指南#1:确保您没有更多地评估传递给宏的表单超过 n 次(n 是绝对需要的次数)
  • 是的,但是您刚刚写道您“尝试使用 gensym 来避免宏扩展中的命名冲突”,我想澄清一下,这里要解决的问题是不同的。
【解决方案3】:

要么将循环开始和循环结束的绑定移动到封闭的 LET 块,要么使用 DO*。原因是 DO 中的所有循环变量都是“并行”绑定的,所以对于第一个绑定,(扩展的)循环开始变量还没有绑定。

【讨论】:

  • 不得不向 kmkaplan 提供 acc。对于最快的手指 - 您的回复也很有帮助 +1
【解决方案4】:

我知道这并不能真正回答您的问题,但我认为它是相关的。根据我的经验,您尝试编写的宏类型是一种非常常见的类型。我对您解决问题的方式的一个问题是它不能处理另一个常见用例:功能组合。

我没有时间强调您在使用宏时可能会遇到的一些困难,但是我会强调,如果您构建了面向函数组合的主要迭代器,您的宏结果非常简单,完全避免了你的问题。

注意:我对你的一些函数做了一些修改。

(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,看看您可以用函数组合做什么很酷的事情。

【讨论】:

  • 将其添加为 TO LEARN - 一旦我完成了 LISP 的学习 :)
【解决方案5】:

我建议完全避免 DO/DO* 和宏,而是使用 Series(可以在 series.sourceforge.net 上找到其实现)。

如果这太复杂了,那么考虑只使用递归或generator(用于按需生成)生成素数列表。

【讨论】:

  • 我正在学习宏。所以这并不能完全解决我的问题。不过感谢分享。
猜你喜欢
  • 2012-08-22
  • 1970-01-01
  • 2013-10-14
  • 1970-01-01
  • 2019-01-18
  • 1970-01-01
  • 1970-01-01
  • 2020-09-13
  • 1970-01-01
相关资源
最近更新 更多