【问题标题】:Is it possible to rewrite recursive function as macro in lisp?是否可以在 lisp 中将递归函数重写为宏?
【发布时间】:2011-01-07 11:03:55
【问题描述】:

我写了这个快速排序函数:

(defun quicksort (lst)
  (if (null lst)
      nil
      (let ((div  (car lst))
            (tail (cdr lst)))
        (append (quicksort (remove-if-not (lambda (x) (< x div)) tail))
                (list div)
                (quicksort (remove-if     (lambda (x) (< x div)) tail))))))

但我不能将它重写为宏,它不起作用,例如,这个简单的 foo(递归求和 - 我知道,有点傻,但只是作为例子):

(defun Suma (lst)
  (if (cdr lst) 
      (+ (Suma (cdr lst))
         (car lst))
      (car lst)))

工作正常,但宏:

(defmacro SumaMacro (lst)
  '(if (cdr lst)
       '(+ (prog (SUMAMACRO (cdr lst)))
           (prog (car lst)))
       '(car lst)))

似乎是错误的。有人对将递归函数重写为宏有什么建议吗?

【问题讨论】:

    标签: recursion lisp common-lisp


    【解决方案1】:

    您正在混合使用宏和运行时;或者换句话说,你混合了值和语法。这是一个非常简单的例子:

    (defmacro while (condition &body body)
      `(when ,condition ,@body (while ,condition ,@body)))
    

    这里的坏处是宏不执行主体,它只是构造一段代码,其中包含给定的主体。因此,当函数中存在这种循环时,它会受到诸如if 之类的条件的保护,这将防止无限循环。但是在这个宏代码中没有这样的条件——你可以看到宏扩展成确切的原始形式,这意味着它试图扩展成一些无限的代码。就像你写的一样

    (defun foo (blah)
      (cons 1 (foo blah)))
    

    然后将该生成器函数挂接到编译器中。所以要执行这些类型的运行时循环,您必须使用一个真正的函数。 (如果需要,您可以使用 labels 创建一个本地函数来执行递归工作。)

    【讨论】:

      【解决方案2】:

      将 SUM 或 QUICKSORT 等递归函数编写为宏是没有意义的。另外,不,一般来说这是不可能的。宏扩展源代码。在编译时,宏只看到源代码,而不是调用代码的真正参数。编译后,宏就消失了,取而代之的是它产生的代码。然后,此代码稍后会使用参数调用。所以宏不能在编译时根据仅在运行时才知道的参数值进行计算。

      例外情况是:当参数值在编译时/宏扩展时已知时,宏可以扩展为对自身的递归宏调用。但这确实是高级宏用法,不会添加到其他程序员维护的代码中。

      经验法则:如果要进行递归计算,请使用函数。如果要处理源代码,请使用宏。

      另外,尝试使用类似 Lisp 的格式。编辑器计算括号,突出显示和缩进。不要把括号放在他们自己的行上,他们在那里感到孤独。通常的 Lisp 风格更紧凑,也更多地使用水平空间。如果您使用列表,请使用 FIRST 和 REST,而不是 CAR 和 CDR。

      您的“suma”函数如下所示:

      (defun suma (list) 
        (if (rest list)
            (+ (suma (rest list))
               (first list))
          (first list)))
      

      忘记宏。但是,如果您想了解有关宏的更多信息,那么 Paul Graham 的《On Lisp》一书(可下载)是一个很好的知识来源。

      【讨论】:

      • 抱歉,我冒昧地将提问者的代码格式化为标准的 Lisp 风格。
      • 我认为最好让它看起来与众不同。 ;-)
      • 有点吹毛求疵:在您的第一段中,不应该是“宏扩展时间”而不是“编译时间”吗?
      • 没错。我在写的时候只是简单地思考了这个问题,但不想深入细节。我想说明编译可能在不同的环境中,例如另一个正在运行的 Lisp,并且程序可能在不同的环境/Lisp 会话中运行。因此,没有办法查找变量的任何运行时值。但是,您是对的,在解释器或宏可能被扩展的其他情况下也会发生同样的情况。
      猜你喜欢
      • 1970-01-01
      • 2011-12-28
      • 1970-01-01
      • 1970-01-01
      • 2014-03-28
      • 2020-09-16
      • 1970-01-01
      • 1970-01-01
      • 2013-05-03
      相关资源
      最近更新 更多