一个Elixir程序可以由Elixir自己的数据结构表示。在本章里,我们将学习那些数据结构看起来是什么样的,以及如何组成它们。在本章中学习的概念是宏的构建块,我们将在下一章中更深入地研究它。
Quoting
一个Elixir程序的构建块上一个三元素的元组。例如,一个函数调用 sum(1, 2, 3) 被内部表示为:
|
|
你通过使用 quote 宏得到任何表达式的表示:
|
|
第一个元素是函数名,第二个元素是一个包含元数据的关键字列表,第三个元素是参数列表。
运算符也可以表示为这样的元组:
|
|
甚至一个映射被表示为对 %{} 的调用:
|
|
变量也可以用这样的三元素的元组来表示,其最后一个元素不是一个列表而是一个原子:
|
|
当引用更负责的表达式,我们可以看到代码用这样的元组来表示,这样的元组经常彼此嵌套为一个像一颗树的结构。许多语言叫这样的表示为抽象语法树(AST)。Elixir叫它们为被引用的表达式:
|
|
有时使用引用表达式时,返回代码的文本表示可能是有用的。这可以用 Macro.to_string/1 :
|
|
总的来说,以上元组按以下格式构造:
|
|
- 第一个元素是一个原子或是相同表示格式的另一个元组。
- 第二个元素是一个包含元数据,比如数字和上下文,的关键字列表。
- 第三个元素是一个函数调用的参数列表或者一个原子。当这个元素是一个原子,则意味着这个元组表示一个变量。
除了上述定义的元组,有五个Elixir字面量,当被引用的时候,返回的是它们自己(而不是一个元组)。它们是:
|
|
大多数Elixir代码有一个它的底层引用表达式的直接翻译。我们建议你尝试不同的代码样例然后看看结果是怎么样的。例如,String.upcase(“foo”) 展开后是什么样子?我们也学到了 if(true, do: :this, else: :that) 和 if true do :this else :that end 是一样的。这个肯定是如何用引用表达式支持的?
Unquoting
quote是关于获取一些特指代码块的内部表示。但是,有时候可能必须注入一些其他特指代码块到我们想要获取的表示里。
例如,你有一个包含数字的变量number,你想要把它注入到一个引用表达式里。
|
|
这不是我们想要的,因为number变量的值没有被注入并且number已经被引入表达式。为了注入number变量的值,unquote必须被使用在被引用的描述里:
|
|
unquote甚至可以被用于注入函数名:
|
|
在一些场景里,可能必须注入许多值在一个列表里。例如,假设你有一个列表 [1, 2, 6] 并且你想把 [3, 4, 5] 注入进去。使用 unquote 不会长生想要的结果:
|
|
这个时候就要使用 unquote_splicing :
|
|
当使用宏的时候,Unquoting 非常有用。当写宏的时候,开发者能够接收代码块并且注入它们到其他代码块里,这能被用来转换代码或者写代码,在编译期间生成代码。
Escaping
正如我们在本章开始的时候所见,在Elixir里只有一些值是有效引用表达式。例如,一个映射不是一个有效引用表达式。有四个元素的元组也不是。但是,这样的值可以被表达为一个引用表达式:
|
|
在一些场景下,你可能需要注入这样的值到引用表达式里。为了这么做,我们需要首先用 Macro.escape/1 帮主来转义那些值为引用表达式:
|
|
宏接收引用表达式并且必须返回引用表达式。然后,有些时候,在宏执行期间,你可能需要使用值并且使得值和将被需求的引用表达式之间有差别。
换句话说,使得一个普通Elixir值(像一个列表、一个映射、一个进程、一个引用等等)和一个引用表达式之间有差别是很重要的。一些值,比如,整数、原子和字符串,有一个引用表达式等于值的本身。其他值,像映射,需要被明确地转换。最后,像函数和引用这样的值完全不能被转换为引用表达式。
在 Kernel.SpecialForms 模块里,你可以阅读到跟多关于 quote 和 unquote 的内容。Macro.escape/1 和其他与引用表达式相关的函数的文档可以在 Macro 模块里找到。
在这篇介绍文章里,我们已经为最终写我们的第一个宏奠定了基础,所以让我们开始下一章。
原文链接: http://elixir-lang.org/getting-started/meta/quote-and-unquote.html