Answers:
将宏用于语法,但不要用于语义。
将宏用于某些语法糖是可以的,但是将逻辑放入宏主体中而不管它是在宏本身中还是在扩展中都是一个坏主意。如果您需要这样做,请改写一个函数,并为语法糖编写一个“伴侣宏”。简而言之,如果您let
的宏中有一个,则表示您正在寻找麻烦。
宏有很多缺点:
根据我的阅读,调试和修改我和其他人的Elisp代码的经验,我建议尽可能避免使用宏。
关于宏的显而易见的事情是它们不评估其参数。这意味着,如果您在宏中包装了一个复杂的表达式,则不知道是否会对它进行评估以及在哪种情况下进行评估,除非您查找宏定义。对于您自己而言,这并不是什么大不了的事情,因为您知道自己的宏的定义(当前,可能在几年后)。
此缺点不适用于函数:所有args均在函数调用之前进行求值。这意味着您可以在未知的调试情况下提高评估复杂度。您甚至可以直接跳过您不知道的函数的定义,只要它们返回简单的数据,并且您假定它们不触及全局状态即可。
用另一种方式改写它,宏成为语言的一部分。如果您正在阅读带有未知宏的代码,那么您几乎会以一种未知的语言来阅读代码。
标准宏喜欢push
,pop
,dolist
为你做的真的很多,而且是众所周知的其他程序员。它们基本上是语言规范的一部分。但是实际上不可能标准化自己的宏。不仅很难像上面的示例那样提出简单有用的东西,而且更难以说服每个程序员学习它。
另外,我不介意像这样的宏save-selected-window
:它们以相同的方式存储和恢复状态并评估其args progn
。非常简单,实际上使代码更简单。
OK宏的另一个示例可以是defhydra
或use-package
。这些宏基本上带有它们自己的迷你语言。而且他们仍然要么对待简单数据,要么表现得像progn
。另外,它们通常位于顶层,因此调用堆栈上没有变量可以依赖宏参数,这使其变得更简单。
一个坏的宏观的一个例子,在我看来,是magit-section-action
和magit-section-case
在Magit。第一个已被删除,但第二个仍然保留。
我会直言不讳:我不明白“用于语法而不是语义”的含义。这就是为什么该答案的一部分将处理lunaryon的答案,而另一部分将是我尝试回答原始问题的原因。
在编程中使用“语义”一词时,是指语言作者选择解释其语言的方式。这通常归结为一组公式,这些公式将语言的语法规则转换为假定读者熟悉的其他规则。例如,普洛特金(Plotkin)在他的《操作语义的结构化方法》中使用逻辑推导语言来解释抽象语法的计算结果(运行程序意味着什么)。换句话说,如果语法在某种程度上构成了编程语言,那么它就必须具有某种语义,否则就不是一种编程语言。
但是,让我们暂时忘记形式定义,毕竟,了解意图很重要。因此,当不向程序添加新含义的时候,lunaryon似乎会鼓励他的读者使用宏。对我来说,这听起来很奇怪,与实际使用宏的方式形成鲜明对比。以下是清楚地产生新含义的示例:
defun
和朋友,这是语言的重要组成部分,无法用描述函数调用的相同术语来描述。
setf
描述函数工作原理的规则不足以描述类似表达式的效果(setf (aref x y) z)
。
with-output-to-string
还可以更改宏中代码的语义。同样,with-current-buffer
还有一堆其他with-
宏。
dotimes
并且在功能上不能描述类似的内容。
ignore-errors
更改其包装的代码的语义。
在cl-lib
package,eieio
package和Emacs Lisp中使用的几乎其他无处不在的库中定义了一堆宏,尽管看起来函数形式有不同的解释。
因此,宏(如果有的话)是将新语义引入语言的工具。我想不出另一种方法(至少在Emacs Lisp中没有)。
当我不使用宏时:
当他们不引入任何具有新语义的新构造时(即该功能将同样出色地完成工作)。
这只是一种便利(例如,创建一个名为的宏mvb
,它会扩展为cl-multiple-value-bind
使其更短)。
当我期望宏将隐藏大量错误处理代码时(如前所述,宏很难调试)。
当我强烈希望宏胜于函数时:
当特定域的概念被函数调用遮盖时。例如,如果需要将相同的context
参数传递给一堆需要连续调用的函数,我宁愿将它们包装在一个context
隐藏参数的宏中。
当函数形式的通用代码过于冗长时(Emacs Lisp中的裸迭代结构太冗长,以至于我不喜欢,并不必要地使程序员暴露于底层实现细节)。
下面是经过简化的版本,旨在使人们对计算机科学中语义的含义有一个直观的认识。
假设您有一种非常简单的编程语言,仅包含以下语法规则:
variable := 1 | 0
operation := +
expression := variable | (expression operation expression)
这种语言我们可以构造“程序”之类(1+0)+1
或0
或((0+0))
等
但是,只要我们不提供语义规则,这些“程序”就毫无意义。
假设我们现在为我们的语言配备(语义)规则:
0+0 0+1 1+0 1+1 (exp)
---, ---, ---, ---, -----
0 1+0 1 0 exp
现在我们可以实际使用这种语言进行计算了。也就是说,我们可以采用语法表示形式,抽象地理解它(换句话说,将其转换为抽象语法),然后使用语义规则来操纵该表示形式。
当我们谈论Lisp语言家族时,函数评估的基本语义规则大致是lambda微积分的规则:
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
引用和宏扩展机制也被称为元编程工具,即它们允许一个谈论有关,而不是仅仅编程程序。他们通过使用“元层”创建新的语义来实现此目的。这样的简单宏的示例是:
(a . b)
-------
(cons a b)
这实际上不是Emacs Lisp中的宏,但它本来可以。我选择只是为了简单起见。注意,上面定义的语义规则都不适用于这种情况,因为最接近的候选者(f x)
将解释f
为一个函数,而a
不一定是一个函数。
(x y)
大致是“评估y,然后用先前评估的结果调用x”。当您编写时(defun x y)
,不会评估y,也不会评估x。是否可以使用不同的语义编写等效效果的代码,并不否认这段具有自己语义的代码。
(defun x y)
与功能应用程序的规则。