Macros( Macros)
宏是Elixir最先进和最强大的功能之一。 与任何语言的所有高级功能一样,应谨慎使用宏。 它们使得在编译时执行强大的代码转换成为可能。 我们现在将了解宏是什么以及如何简单地使用它们。
Quote
在我们开始讨论宏之前,让我们先来看看Elixir内部。 Elixir程序可以由其自己的数据结构表示。 Elixir程序的构建块是一个包含三个元素的元组。 例如,函数调用sum(1,2,3)在内部表示为 -
{:sum, [], [1, 2, 3]}
第一个元素是函数名称,第二个元素是包含元数据的关键字列表,第三个是参数列表。 如果您编写以下内容,可以将此作为iex shell中的输出 -
quote do: sum(1, 2, 3)
运算符也表示为这样的元组。 变量也使用这样的三元组来表示,除了最后一个元素是原子而不是列表。 当引用更复杂的表达式时,我们可以看到代码在这样的元组中表示,这些元组通常在类似于树的结构中彼此嵌套。 许多语言将此类表示称为Abstract Syntax Tree (AST) 。 Elixir称这些引用的表达式。
Unquote
现在我们可以检索代码的内部结构,我们如何修改它? 要注入新的代码或值,我们使用unquote 。 当我们取消引用表达式时,它将被评估并注入AST。 让我们考虑一个例子(在iex shell中)来理解这个概念 -
num = 25
quote do: sum(15, num)
quote do: sum(15, unquote(num))
运行上述程序时,会产生以下结果 -
{:sum, [], [15, {:num, [], Elixir}]}
{:sum, [], [15, 25]}
在引用表达式的示例中,它没有自动将num替换为25.如果我们想要修改AST,则需要取消引用此变量。
Macros
所以现在我们熟悉quote和unquote,我们可以使用宏来探索Elixir中的元编程。
在最简单的术语中,宏是特殊函数,用于返回将插入到我们的应用程序代码中的带引号的表达式。 想象一下,宏被引用的表达式替换而不是像函数一样调用。 使用宏,我们拥有扩展Elixir并动态添加代码到应用程序所需的一切
除非作为宏,否则让我们实施。 我们将首先使用defmacro宏定义宏。 请记住,我们的宏需要返回一个带引号的表达式。
defmodule OurMacro do
defmacro unless(expr, do: block) do
quote do
if !unquote(expr), do: unquote(block)
end
end
end
require OurMacro
OurMacro.unless true, do: IO.puts "True Expression"
OurMacro.unless false, do: IO.puts "False expression"
运行上述程序时,会产生以下结果 -
False expression
这里发生的是我们的代码正在被unless宏返回的引用代码所取代。 我们没有引用该表达式来在当前上下文中对其进行求值,并且还没有引用do块来在其上下文中执行它。 这个例子向我们展示了在elixir中使用宏的元编程。
宏可用于更复杂的任务,但应谨慎使用。 这是因为元编程通常被认为是一种不好的做法,只应在必要时使用。