Elixir - 宏

宏是 Elixir 最先进、最强大的功能之一。 与任何语言的所有高级功能一样,应谨慎使用宏。 它们使得在编译时执行强大的代码转换成为可能。 我们现在将了解什么是宏以及如何简要使用它们。

引用(Quote)

在我们开始讨论宏之前,让我们先看看 Elixir 的内部结构。 Elixir 程序可以用它自己的数据结构来表示。 Elixir 程序的构建块是一个包含三个元素的元组。 例如,函数调用 sum(1, 2, 3) 在内部表示为 −

{:sum, [], [1, 2, 3]}

第一个元素是函数名称,第二个元素是包含元数据的关键字列表,第三个元素是参数列表。 如果编写以下内容,则可以将其作为 iex shell 中的输出获取 −

quote do: sum(1, 2, 3)

运算符也表示为这样的元组。 变量也使用这样的三元组来表示,除了最后一个元素是原子而不是列表。 当引用更复杂的表达式时,我们可以看到代码是用这样的元组表示的,这些元组通常以类似于树的结构相互嵌套。 许多语言将此类表示称为抽象语法树(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,我们需要取消引用该变量。

现在我们已经熟悉了引用和取消引用,我们可以使用宏来探索 Elixir 中的元编程。

用最简单的术语来说,宏是特殊函数,旨在返回将插入到我们的应用程序代码中的带引号的表达式。 想象一下宏被替换为带引号的表达式,而不是像函数一样被调用。 有了宏,我们就有了扩展 Elixir 和动态添加代码到我们的应用程序所需的一切

让我们将 except 作为宏来实现。 我们将首先使用 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 中使用宏进行元编程。

宏可以用于更复杂的任务,但应谨慎使用。 这是因为元编程通常被认为是一种不好的做法,应该仅在必要时使用。