是否有任何宏不能表示为函数?

时间:2015-11-17 10:55:18

标签: function macros lisp

是否有

  • 无法表示为等效的功能,或者:
  • 难度表示为等效的功能,或者:
  • 效果方面明显比同等功能差得多?

你能给出一个示例这样的(和函数)吗?

我的问题专门针对Lisp的宏和函数,但您可以更一般地处理这个问题。我对递归宏特别感兴趣。

修改

我应该更具体。当我问上面的问题时,我记得我的 Lisp 项目的特定上下文,这是一种数学symbolic programming计算器。

我想知道是否有充分的理由将宏用于任何数学运算,例如:

出于某种原因,我需要为此项目使用一些递归宏。所以,如果我可以重新提出我的问题:

  

您能举例说明在符号计算中智能使用[递归]宏吗?

4 个答案:

答案 0 :(得分:3)

  

是否有

     
      
  • 无法表示为等效的功能,或者:
  •   
  • 难度表示为等效的功能,或者:
  •   
  • 效果方面明显比同等功能差得多?
  •   

答案从来都不是那么简单。它通常是“是和否”。正如我所看到的,宏有两个很大的优点:语法糖延迟评估。例如,像 with-open-file 这样的宏,可以让你写:

(with-open-file (var "some-filename")
  ; operations with var
  )

几乎只是

的语法糖
(let ((x (open ...)))
  (unwind-protect 
    (funcall (lambda (var) 
               ; operations with var
               )
              x)
    ; cleanup forms
    ))

这是一种延迟评估和语法糖的混合,取决于你如何看待它。这是延迟评估,因为var的所有操作都包含在lambda函数中。它也是语法糖,因为你显然可以将上述内容抽象为:

(defun call-with-open-file (open-args function)
  (let ((x (apply 'open open-args)))
    (unwind-protect (funcall function x)
      ; cleanup forms
      )))

然后 with-open-file 只是语法糖:

(defmacro with-open-file ((var &rest open-args) &body body)
  `(call-with-open-file (list ,@open-args)
     (lambda (,var) ,@body)))

这是一个典型的案例,展示了(功能形式的)延迟评估和功能界面周围的语法糖。您通常可以始终这样做。例如,使用 if ,您可以编写一个功能界面:

(defun %if (condition then &optional (else (constantly nil)))
  `(funcall (cond (condition then) (t else))))

然后如果可以实现为宏:

(defmacro if (condition then &optional else)
  `(%if condition (lambda () ,then) (lambda () ,else)))

如果和其他条件形式有点独特,但在这个意义上,因为实现最终必须为您提供某些条件操作。但是,该运算符通常不是,而是一种特殊形式。

其他特殊的宏如循环,它们定义了特定于域的语言?你也可以这样做,但你几乎最终会让函数接受宏版本的“体”并在运行时解释它。例如,你可以做到

(defun %loop (&rest loop-body)
  ; interpret body
  )

但这显然会成为一个重大的表现。

所以,我认为没有没有语义等价的宏,但这些需要一些不同的参数。其中一些语义上等效的函数难以表达,其中一些(例如,当传递匿名函数时)肯定会有明显更差的性能。

答案 1 :(得分:2)

我认为你提出的问题不是很好,因为它是基于对“等价”一词的模糊用法。乍一看,您似乎打算将“等效”视为:“计算相同的值”(这可以通过您关于性能的第三个问题得到证实)。

但它们根本不是等价的,因为函数产生(或计算)值,而宏产生(或计算)程序! (当你理解这一点时,你会理解一个宏实际上一个函数,一个从s表达式(“引用的参数”)到s表达式的函数。)

所以,我认为你的问题的答案应该用这些术语来表达:

1)如果你将等价的意义延伸为“当宏的结果(即程序)被系统进一步评估时”,那么应该考虑像约书亚泰勒那样的答案;

2)如果您询问宏和功能本身,它们不等同

关于它们在你正在解决的任务中的使用:宏在定义特定的控制结构或执行计算的专门方法(如DSL(领域特定语言))中非常有用,但我的建议是使用它们当您认为通过添加常用工具(即预定义函数,特殊表单和宏)以及更新的强大工具时,您可以更轻松地解决问题编写复杂宏的经验(为了实践这一点,请参阅Paul Graham的书On Lisp)。

答案 2 :(得分:1)

  

是否有任何无法表示为函数的宏?

是;任何专业编写的Lisp程序中的所有宏!

如果您从根本上改变或扩充基础语言实现,有时会将 替换为函数的宏。

例如,宏可能正在模拟某些东西,否则需要在没有它们的Lisp方言中继续。或者它可能会通过严格的语言做一些看似非严格评估的事情。只需使用按名称调用语言中的函数就可以用纯值按值调用宏来表示。

宏扩展时会消失;剩下的就是特殊的操作员和功能。哪些功能可以做或不可以取决于可用的特殊操作员。例如,如果没有运算符来捕获延续,函数就不能放弃它的评估,以便以后可以重新启动它。

因此,考虑在宏和函数之间划分权力而忽略特殊运算符是错误的二分法。

给定的问题可以通过功能和特殊操作符的组合来解决。如果它需要某些特殊的运营商,那么我们就不能这么说了 只有功能才能解决问题。

可以使用宏来隐藏特殊运算符的使用。隐藏特殊操作符必要用途的宏不能重写为函数。

例如,在lambda运算符上提供语法糖的宏不能写为函数。宏的基本功能取决于它扩展为lambda运算符的事实,该运算符捕获宏调用发生的原始词法环境中的闭包。

当Lisp语言设计者使用新的核心功能扩展方言时,他们会通过添加新的特殊表单来实现。同时添加宏以使表单更易于使用。例如,我最近添加了一个Lisp方言的分隔延续。底层API对于某些简单任务来说并不是最容易使用的,所以我还提供了一个宏,它提供了一个易于使用的“生成器”抽象。不用说,这些生成器宏不能用函数实现。不仅如此,在没有分隔连续支持的情况下,这些宏无法实现 。他们所做的只是编写依赖于使用这些新特殊表单的代码,而这些特殊表单是由深入语言核心的黑客实现的,它们会执行令人讨厌的事情,例如将运行时堆栈的部分复制到堆中,然后返回到不同的区域堆栈。

现在,在一个纯粹解释性的Lisp中,通过评估原始源代码来运行程序,你可以拥有一种与宏一样强大的函数形式(实际上更是如此)。这是一个函数,当它在运行时调用时,接收其未评估的参数表达式,以及评估它们所需的运行时环境。本质上,这样的功能虽然由用户编写,但却充当“解释器插件”,需要以任意方式解释代码。在历史性的Lisp术语中,这种函数称为“fexpr”。

宏与fexprs之间的关系是宏是指编译器对解释器的作用。如果你有一个带有fexprs的方言,那么没有理由使用宏,如果唯一的要求是支持某些语义的语法,而不关心性能。宏可以通过编译为更有效的翻译来做同样的事情。尽管方言纯粹是解释性的,但是让解释器运行一些宏生成的代码比解释器解释一个本身解释代码的函数更快。

但是,当然,虽然fexprs是函数,但它们不是普通的函数;普通函数接收评估的参数而没有环境。所以这只是将问题改为:是否存在普通函数无法替换的基本函数?

答案是:是的,在专业编写的程序中的任何一个fexprs ......

答案 3 :(得分:0)

任何不评估的Lisp宏(“两次”,因为它是一个宏)其参数不能表示为函数,因为函数应用程序是在计算的参数上完成的。例如,您可以定义宏my-if,其行为与if完全相同(而if 不能成为函数)

C.Queinnec的书Lisp In Small Pieces非常详细地解释了(并且有几章关于宏)。我强烈建议您阅读它(因为回答过于宽泛的问题可能需要整本书,而不是段落。)

如果一个宏多次扩展其中一个参数,它可能比等效函数慢(因为如果扩展两次,一些子计算可以完成两次)。

(当然,你所有问题的答案都可以是;我会告诉你如何找到一些例子)。

PS。顺便说一句,在C ......中甚至是这样。