Elixir - 宏


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

引用

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

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

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

quote do: sum(1, 2, 3)

运算符也表示为这样的元组。变量也使用这样的三元组来表示,除了最后一个元素是Atomics而不是列表。当引用更复杂的表达式时,我们可以看到代码是用这样的元组表示的,这些元组通常以类似于树的结构相互嵌套。许多语言将这种表示称为抽象语法树(AST)。Elixir 将这些称为引用表达式。

取消引用

现在我们可以检索代码的内部结构,我们如何修改它呢?要注入新的代码或值,我们使用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 

这里发生的情况是我们的代码被except宏返回的引用代码所替换。我们取消了表达式的引号以在当前上下文中对其求值,并且取消了 do 块的引号以在其上下文中执行它。这个例子向我们展示了在 Elixir 中使用宏进行元编程。

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