Elixir - 宏



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

引用

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

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

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

quote do: sum(1, 2, 3)

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

解引用 (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]} 

在 quote 表达式的示例中,它没有自动将 num 替换为 25。如果要修改 AST,则需要解引用此变量。

既然我们已经熟悉了 quote 和 unquote,我们可以使用宏来探索 Elixir 中的元编程。

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

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

宏可以用于更复杂的任务,但应谨慎使用。这是因为元编程通常被认为是不好的实践,只有在必要时才应使用。

广告