HY 等Lisp语言最有特性的就是宏了,如果其它语言也实现了Lisp里的宏,那么它们就成为了一种Lisp方言,由此可见宏的独特性。
先看一下前一章讲的宏的例子:HY编程快速入门实践课第二章 跟着python学HY-CSDN博客
宏是 Lisp 的基本元编程工具。宏是在编译时(即,当 Hy 程序被转换为 Python ast 对象时)调用并返回代码的函数,代码成为最终程序的一部分。下面是一个简单的示例:
(print "Executing")
(defmacro m []
(print "Now for a slow computation")
(setv x (% (** 10 10 7) 3))
(print "Done computing")
x)
(print "Value:" (m))
(print "Done executing")
(setv x (% (** 10 10 7) 3))的计算量较大,计算机会有明显的计算时间卡顿。在交互模式下执行上面语句每次都需要较长时间,且输出一样,都有“Now for a slow computation”。将内容写入testmacro.hy文件后,执行hy testmacro.hy2次,会发现两次执行的输出不一样,第一次有这句而第二次没有“Now for a slow computation”,且第二次执行的时间大大缩短。
下面我们开始从头学宏
宏有什么用?¶
元编程的要点是它允许您对编程语言本身进行编程(记住这个词)。您可以创建新的控件结构(如 do-while)或其他类型的新语法(例如用于您喜欢的数据结构的简洁文字表示法)。您还可以修改在代码区域中对现有语法的理解方式,例如通过创建从特定模块隐式导入的大写字母开头的标识符。最后,在某些情况下,元编程可以通过有效地内联函数来提高性能,或者在编译时计算一次而不是在运行时计算几次。使用类似 Lisp 的宏系统,您可以以一种更流畅、更不容易出错的方式进行元编程,而不是使用传统的字符串格式或词法分析器级宏(如 C 预处理器提供的宏)将代码生成为文本。
宏的类型
Hy 提供两种类型的宏:常规宏Regular macros和阅读器宏Reader macros。
Regular macros,
常规宏,通常用 defmacro
定义,是 Lispers 在谈论“宏”时通常指的那种。常规宏的调用方式类似于函数,其表达式的头部是宏名称:例如, (foo a b)
可以调用名为 foo
的宏。在分析常规宏所在的整个顶级表单之后,在编译时调用常规宏,并接收解析后的模型作为参数。常规宏有三种类型,范围各不相同。
Reader macros,
读取器宏(通常用 defreader
定义)比常规宏级别低。它们用哈希符号 #
调用;例如, #foo
调用名为 foo
的读取器宏。读取器宏在解析时调用。它没有收到常规的参数(arguments)。相反,它使用隐式可用的分析器对象来分析后续源文本。当它返回时,标准 Hy 解析器会从中断的地方继续。
Related constructs 相关构造 ¶
还有另外三种构造可以执行编译时处理,就像宏一样,因此这里值得一提。
do-mac is essentially shorthand for defining and then immediately calling a regular macro with no arguments.
do-mac
本质上是定义然后立即调用没有参数的常规宏的简写。eval-when-compile
eval-when-compile
在编译时评估某些代码,但不向最终程序贡献任何代码,例如在不执行任何操作的上下文None
中返回None
的宏。eval-and-compile
eval-and-compile
在编译时计算某些代码,例如eval-when-compile
,但也保留相同的代码在运行时重新计算。
何时使用
在决定使用什么时,一个好的经验法则是使用功能最弱的选项,该选项足以满足所需的语法、语义和性能。所以首先,看看 Python 的动态特性是否足够。如果不是,请尝试类似宏的构造或常规宏。如果这些还不够,请尝试阅读器宏。使用功能最弱的适用选项将帮助您避免下面描述的宏陷阱,以及其他令人头疼的问题,例如想要在 Python API 需要函数的情况下使用宏。(为了提供更简单的示例,下面的大部分讨论将忽略此建议,并考虑可以很容易地编写为函数的宏。
The basics 基础知识 ¶
可以使用类似于 的 defn
语法来定义 defmacro
常规宏。下面介绍了如何定义和调用一个不接受参数并返回常量的琐碎宏( a trivial macro):
(defmacro seventeen [] 17) (print (seventeen))
To see that seventeen
is expanded at compile-time, run hy2py
on this script and notice that it ends with print(17)
rather than print(seventeen())
. If you insert a print
call inside the macro definition, you'll also see that the print happens when the file is compiled, but not when it's rerun (so long as an up-to-date bytecode file exists).
若要查看在编译时扩展的内容 seventeen
,请在此脚本上运行 hy2py
$ hy2py macro17.hy
import hy
hy.macros.macro('seventeen')(lambda : 17)
print(17)
注意它以 print(17)
结尾而不是 print(seventeen())
结尾 。如果在宏定义中插入 print
调用,您还将看到打印在编译文件时发生,但在重新运行时不会发生(只要存在最新的字节码文件)。
A more useful macro returns code. You can construct a model the long way, like this:
更有用的宏返回代码。可以用很长的方式构造一个模型,如下所示:
(defmacro addition [] (hy.models.Expression [ (hy.models.Symbol "+") (hy.models.Integer 1) (hy.models.Integer 1)]))
or more concisely with quote, like this:
或者更简洁地说 quote
,像这样:
(defmacro addition [] '(+ 1 1))
You don't need to always return a model because the compiler calls hy.as-model on everything before trying to compile it. Thus, the 17
above works fine in place of (hy.models.Integer 17)
. But trying to compile something that hy.as-model
chokes on, like a function object, is an error.
您不需要总是返回模型,因为编译器在尝试编译模型之前会调用 hy.as-model
所有内容。因此, 上面17
可以代替 (hy.models.Integer 17)
.但是试图编译 hy.as-model
一些窒息的东西,比如函数对象,是一个错误。
Arguments are always passed in as models. You can use quasiquotation (see quasiquote) to concisely define a model with partly literal and partly evaluated components:
参数始终作为模型传入。您可以使用准引号(请参阅 quasiquote
)来简明扼要地定义一个模型,该模型包含部分文字和部分计算的组件:
(defmacro set-to-2 [variable] `(setv ~variable 2)) (set-to-2 foobar) (print foobar)
宏不像函数那样理解关键字参数。相反,关键字对象本身是按字面意思传入的。这使您可以灵活地处理它们。因此, #** kwargs
和*
在宏的参数列表中不允许, 尽管 #* args
和 /
是允许的。查看 hyrule.defmacro-kwargs
是否要像处理函数一样处理关键字参数。
On the inside, macros are functions, and obey the usual Python semantics for functions. For example, setv inside a macro will define or modify a variable local to the current macro call, and return ends macro execution and uses its argument as the expansion.
在内部,宏是函数,并遵循函数的通常 Python 语义。例如, setv
在宏内部将定义或修改当前宏调用的本地变量,并 return
结束宏执行并使用其参数作为扩展。
Macros from other modules can be brought into the current scope with require.
其他模块中的宏可以使用 require
引入当前范围。
Pitfalls 陷阱 ¶
Macros are powerful, but with great power comes great potential for anguish. There are a few characteristic issues you need to guard against to write macros well, and, to a lesser extent, even to use macros well.
宏是强大的,但强大的力量带来了巨大的痛苦潜力。要编写好宏,甚至在较小程度上,甚至要很好地使用宏,您需要防范一些特征问题。
Name games 命名游戏 ¶
其中很多问题都是名称主题的变体,而不是引用您的意图,或者换句话说,意外的名称屏蔽。例如,下面的宏旨在定义一个名为 x
的新变量,但它最终修改了一个预先存在的变量。
(defmacro upper-twice [arg] `(do (setv x (.upper ~arg)) (+ x x))) (setv x "Okay guys, ") (setv salutation (upper-twice "bye")) (print (+ x salutation)) ; Intended result: "Okay guys, BYEBYE" ; Actual result: "BYEBYEBYE"
如果通过多次使用参数来完全避免赋值,则可能会导致不同的问题:出人意料的多重评估。
(defmacro upper-twice [arg] `(+ (.upper ~arg) (.upper ~arg))) (setv items ["a" "b" "c"]) (print (upper-twice (.pop items))) ; Intended result: "CC" ; Actual result: "CB"
更好的方法是选择 hy.gensym
变量名称:
(defmacro upper-twice [arg] (setv g (hy.gensym)) `(do (setv ~g (.upper ~arg)) (+ ~g ~g)))
Hyrule 提供了一些宏,让使用 gensym 更加方便,例如 defmacro! 和 with-gensyms.。
Macro subroutines 宏子程序 ¶
您可能希望某些内容在宏的扩展范围内,但结果却不是,这种情况是当您想在扩展中调用函数或其他宏时:
(defmacro hypotenuse [a b] (import math) `(math.sqrt (+ (** ~a 2) (** ~b 2)))) (print (hypotenuse 3 4)) ; NameError: name 'math' is not defined
The form (import math)
here appears in the wrong context, in the macro call itself rather than the expansion. You could use import
or require
to bind the module name or one of its members to a gensym, but an often more convenient option is to use the one-shot import syntax hy.I or the one-shot require syntax hy.R:
此处的形式 (import math)
出现在错误的上下文中,在宏调用本身而不是扩展中。您可以使用 import
或 require
将模块名称或其成员之一绑定到 gensym,但通常更方便的选择是使用 one-shot import 语法 hy.I
或 one-shot require 语法 hy。R:
(defmacro hypotenuse [a b] `(hy.I.math.sqrt (+ (** ~a 2) (** ~b 2)))) (print (hypotenuse 3 4))
一个相关但明显的问题是,当您想在宏的代码中使用函数(或其他普通的 Python 对象)时,但它还不够快:
(defn subroutine [x] (hy.models.Symbol (.upper x))) (defmacro uppercase-symbol [x] (subroutine x)) (setv (uppercase-symbol foo) 1) ; NameError: name 'subroutine' is not defined
Here, subroutine
is only defined at run-time, so uppercase-symbol
can't see it when it's expanding (unless you happen to be calling uppercase-symbol
from a different module). This is easily worked around by wrapping (defn subroutine …)
in eval-and-compile (or eval-when-compile if you want subroutine
to be invisible at run-time).
在这里, subroutine
仅在运行时定义,因此 uppercase-symbol
在扩展时看不到它(除非您碰巧从不同的模块调用 uppercase-symbol
)。这可以通过包装 (defn subroutine …)
eval-and-compile
(或者 eval-when-compile
如果你想 subroutine
在运行时不可见)轻松解决。
By the way, despite the need for eval-and-compile
, extracting a lot of complex logic out of a macro into a function is often a good idea. Functions are typically easier to debug and to make use of in other macros.
顺便说一句,尽管需要 eval-and-compile
,将大量复杂的逻辑从宏中提取到函数中通常是一个好主意。函数通常更易于调试,也更易于在其他宏中使用。
The important take-home big fat WARNING
重要的带回家的大胖子警告 ¶
Ultimately it's wisest to use only four kinds of names in macro expansions: gensyms, core macros, objects that Python puts in scope by default (like its built-in functions), and hy
and its attributes. It's possible to rebind nearly all these names, so surprise shadowing is still theoretically possible. Unfortunately, the only way to prevent these pathological rebindings from coming about is… don't do that. Don't make a new macro named setv
or name a function argument type
unless you're ready for every macro you call to break, the same way you wouldn't monkey-patch a built-in Python module without thinking carefully. This kind of thing is the responsibility of the macro caller; the macro writer can't do much to defend against it. There is at least a pragma warn-on-core-shadow, enabled by default, that causes defmacro
and require
to warn you if you give your new macro the same name as a core macro.
归根结底,最明智的做法是在宏扩展中只使用四种名称:gensyms、核心宏、Python 默认放在范围内的对象(如其内置函数)及其 hy
属性(gensyms, core macros, objects that Python puts in scope by default (like its built-in functions), and hy
and its attributes.)。几乎可以重新绑定所有这些名称,因此理论上仍然可以进行意外阴影。不幸的是,防止这些病理性重新结合发生的唯一方法是......别这样。不要为新宏命名 setv
或命名函数参数 type
,除非你准备好让你调用的每个宏都中断,就像你不会在不仔细思考的情况下对内置 Python 模块进行猴子修补一样。这种事情是宏调用者的责任;宏编写者无法做太多事情来防御它。默认情况下,至少有一个 pragma warn-on-core-shadow(默认启用),如果您为新宏指定与核心宏相同的名称,则会导致 defmacro
并 require
警告您。
Reader macros 阅读器宏 ¶
Reader macros allow you to hook directly into Hy's parser to customize how text is parsed into models. They're defined with defreader, or, like regular macros, brought in from other modules with require. Rather than receiving function arguments, a reader macro has access to a hy.HyReader object named &reader
, which provides all the text-parsing logic that Hy uses to parse itself (see hy.HyReader and its base class hy.Reader for the available methods). A reader macro is called with the hash sign #
, and like a regular macro, it should return a model or something convertible to a model.
阅读器宏允许您直接挂接到 Hy 的解析器中,以自定义文本解析为模型的方式。它们使用 defreader
定义,或者像常规宏一样,使用 require
从其他模块引入。reader 宏可以访问名为 的 hy.HyReader
,而不是接收函数参数,该对象提供了 Hy 用于解析自身的所有文本解析逻辑(有关可用方法,请参阅 hy.HyReader
及其基类 hy.Reader
)。 &reader
读取器宏是用哈希符号 #
调用的,与常规宏一样,它应该返回一个模型或可转换为模型的东西。
The simplest kind of reader macro doesn't read anything:
最简单的阅读器宏不会读取任何内容:
(defreader hi `(print "Hello.")) #hi #hi #hi
A less trivial, and more common, usage of reader macros is to call hy.HyReader.parse_one_form() to get a single form from the following source text. Such a reader macro is like a unary regular macro that's called with #
instead of parentheses.
读取器宏的一个不太简单且更常见的用法是从以下源文本中 hy.HyReader.parse_one_form()
调用以获取单个表单。这样的读取器宏就像一个一元正则宏,用 #
括号而不是括号来调用。
(defreader do-twice (setv x (.parse-one-form &reader)) `(do ~x ~x)) #do-twice (print "This line prints twice.")
Here's a moderately complex example of a reader macro that couldn't be implemented as a regular macro. It reads in a list of lists in which the inner lists are newline-separated, but newlines are allowed inside elements.
下面是一个中等复杂的读取器宏示例,该宏无法作为常规宏实现。它读取列表列表,其中内部列表以换行符分隔,但允许在元素内部使用换行符。
(defreader matrix (.slurp-space &reader) (setv start (.getc &reader)) (assert (= start "[")) (.slurp-space &reader) (setv out [[]]) (while (not (.peek-and-getc &reader "]")) (cond (any (gfor c " \t" (.peek-and-getc &reader c))) None (.peek-and-getc &reader "\n") (.append out []) True (.append (get out -1) (.parse-one-form &reader)))) (lfor line out :if line line)) (print (hy.repr #matrix [ 1 (+ 1 1) 3 4 ["element" "containing" "a" "newline"] 6 7 8 9])) ; => [[1 2 3] [4 ["element" "containing" "a" "newline"] 6] [7 8 9]]
Note that because reader macros are evaluated at parse-time, and top-level forms are completely parsed before any further compile-time execution occurs, you can't use a reader macro in the same top-level form that defines it:
请注意,由于读取器宏是在解析时计算的,并且顶级表单在发生任何进一步的编译时执行之前被完全分析,因此不能在定义它的同一顶级表单中使用读取器宏:
(do (defreader up (.slurp-space &reader) (.upper (.read-one-form &reader))) (print #up "hello?")) ; LexException: reader macro '#up' is not defined
Of the potential problems discussed above that apply to regular macros, such as surprise shadowing, most also apply to reader macros.
在上面讨论的适用于常规宏的潜在问题(例如意外阴影)中,大多数也适用于阅读器宏。
宏命名空间和宏操作 ¶
Macros don't share namespaces with ordinary Python objects. That's why something like (defmacro m []) (print m)
fails with a NameError
, and how hy.pyops can provide a function named +
without hiding the core macro +
.
宏不与普通 Python 对象共享命名空间。这就是为什么像 (defmacro m []) (print m)
之类的东西会失败的原因 NameError
,以及如何 hy.pyops
在不隐藏核心宏的情况下提供命名 +
的函数 +
。
There are three scoped varieties of regular macro. First are core macros, which are built into Hy; the set of core macros is fixed. They're available by default. You can inspect them in the dictionary bulitins._hy_macros
, which is attached to Python's usual builtins module. The keys are strings giving mangled names and the values are the function objects implementing the macros.
常规宏有三种作用域变体。首先是核心宏,它们内置于 Hy 中;核心宏集是固定的。默认情况下,它们可用。您可以在字典中检查它们 bulitins._hy_macros
,该字典附加到 Python 的常用 builtins
模块。键是给出乱七八糟的名称的字符串,值是实现宏的函数对象。
Global macros are associated with modules, like Python global variables. They're defined when you call defmacro
or require
in a global scope. You can see them in the global variable _hy_macros
associated with the same module. You can manipulate _hy_macros
to list, add, delete, or get help on macros, but be sure to use eval-and-compile or eval-when-compile when you need the effect to happen at compile-time, which is often. (Modifying bulitins._hy_macros
is of course a risky proposition.) Here's an example, which also demonstrates the core macro get-macro. get-macro
provides syntactic sugar for getting all sorts of macros as objects.
全局宏与模块相关联,例如 Python 全局变量。它们是在调用 defmacro
时或 require
在全局范围内定义的。您可以在与同一模块 _hy_macros
关联的全局变量中看到它们。您可以操作 _hy_macros
以列出、添加、删除宏或获取有关宏的帮助,但请务必在编译时使用 eval-and-compile
或 eval-when-compile
需要效果时发生,这通常是。(修改 bulitins._hy_macros
当然是一个有风险的提议。下面是一个示例,它还演示了 get-macro
核心宏 。 get-macro
提供语法糖,用于将各种宏作为对象。
(defmacro m [] "This is a docstring." `(print "Hello, world.")) (print (in "m" _hy_macros)) ; => True (help (get-macro m)) (m) ; => "Hello, world." (eval-and-compile (del (get _hy_macros "m"))) (m) ; => NameError (eval-and-compile (setv (get _hy_macros (hy.mangle "new-mac")) (fn [] '(print "Goodbye, world.")))) (new-mac) ; => "Goodbye, world."
Local macros are associated with function, class, or comprehension scopes, like Python local variables. They come about when you call defmacro
or require
in an appropriate scope. You can call local-macros to view local macros, but adding or deleting elements is ineffective.
局部宏与函数、类或推导范围相关联,如 Python 局部变量。它们在您打电话 defmacro
时或 require
在适当的范围内出现。您可以调用 local-macros
以查看本地宏,但添加或删除元素无效。
Finally, _hy_reader_macros
is a per-module dictionary like _hy_macros
for reader macros, but here, the keys aren't mangled. There are no local reader macros, and there's no official way to introspect on Hy's handful of core reader macros. So, of the three scoped varieties of regular macro, reader macros most resemble global macros.
最后, _hy_reader_macros
是一个像阅读器宏一样 _hy_macros
的每模块字典,但在这里,键没有被破坏。没有本地读者宏,也没有官方的方法来反省 Hy 的少数核心读者宏。因此,在常规宏的三种作用域变体中,读取器宏最类似于全局宏。