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

Are there any macros that cannot be expressed as a function?

有没有

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

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


编辑:

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

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

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

Can you give examples of smart use of [recursive] macros in a symbolic calculation?

任何不计算的 Lisp 宏("twice",因为它是一个宏)它的参数不能表示为一个函数,因为函数应用是在 evaluated[=28= 上完成的] 争论。例如,您可以定义一个宏 my-if,其行为与 if 完全相同(并且 if 不能 是一个函数)

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

如果宏将其参数之一展开多次,它可能比等效函数慢(因为如果展开两次,某些子计算可能会完成两次)。

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

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

Are there any macros that:

  • Cannot be expressed as a equivalent function, or:
  • Are difficult to express as a equivalent function, or:
  • Are significantly worse in terms of performance than equivalent function?

答案绝不会这么简单。它通常是 "yes and no"。在我看来,宏有两大优势:语法糖延迟求值。例如,像 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)))

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

其他定义领域特定语言的特殊宏(如 loop 呢?你也可以这样做,但你几乎只会让函数接受宏版本的 "body" 并在运行时解释它。例如,你可以做

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

但这显然会对性能造成很大影响。

因此,我认为没有宏不具有语义等价物,但这些将需要稍微不同的参数。其中一些语义等价的函数 难以表达,其中一些(例如,在传递匿名函数时)的性能肯定会差得多。

我认为你的问题提出得不好,因为它是基于对“等效”一词的模糊使用。乍一看,您似乎打算“等效”为:“计算相同的值”(并且您关于性能的第三个问题证实了这一点)。

但它们根本不等价,因为函数产生(或计算)值,而宏产生(或计算)程序! (当你明白这一点时,你就会明白宏实际上 一个函数,一个从 s-expressions(“带引号的参数”)到 s-expressions 的函数。

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

1) 如果将等价的含义延伸为“当宏(即程序)的结果被系统进一步评估时”,那么像 Joshua Taylor 这样的答案将被考虑考虑;

2) 如果您询问的是宏和函数本身,它们根本不等价

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

Are there any macros that cannot be expressed as a function?

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

如果您从根本上更改或扩充底层语言实现,有时 可以 将宏替换为函数。

例如,一个宏可能正在模拟一些东西,否则需要在没有它们的 Lisp 方言中继续。或者它可能正在通过严格的语言做一些看起来像非严格评估的事情。有些事情可以用函数完成,在按名称调用的语言中可以用宏来表达,而不是纯按值调用。

宏展开后消失;剩下的就是特殊的运算符和函数。函数能做什么或不能做什么取决于可用的特殊运算符。例如,如果没有运算符来捕获延续,函数就不能以稍后可以重新启动的方式放弃其评估。

因此,将功能划分为宏和函数而忽略特殊运算符是错误的二分法。

给定的问题可以通过函数和特殊运算符的组合来解决。如果它需要某些特殊的操作符,那么我们不能说 仅靠函数解决问题

宏的使用方式可以隐藏特殊运算符的使用。隐藏特殊运算符基本用途的宏不能重写为函数。

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

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

现在,在 运行 通过评估原始源代码编写程序的纯解释性 Lisp 中,您可以拥有一种与宏一样强大的函数形式(事实上,更是如此)。这是一个函数,当它在 运行 时间被调用时,接收其未计算的参数表达式,以及评估它们所需的 运行 时间环境。本质上,这样的函数虽然是由用户编写的,但充当 "interpreter plugin",被调用以任意方式解释代码。在历史悠久的 Lisp 术语中,这种函数称为 "fexpr".

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

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

答案是:是的,专业编写的程序中的任何 fexpr ...