quote与unquote属于elixir对meta-programming(元编程)的支持,使开发者拥有改变语言核心的能力(增加自定义核心函数, 扩展语言核心)。Elixir程序的构建块由一个三元素的元组组成,列如函数sum(1, 2, 3)的内部表述为(AST abstract syntax tree 抽象语法书):
{:sum, [], [1, 2, 3]}
我们可以在iex中使用
iex(1)> quote do: sum(1,2,3)
{:sum, [], [1, 2, 3]}
查看。AST元组中第一个元素是函数名,第二个元素是包含元数据的键值对列表(此处为空),第三个元素是函数参数列表。所以quote函数提供一种得到函数(函数式语言一切皆函数)的元信息,这些信息就是系统用来编译出最终运行文件的重要数据。当我们有了这些元数据后,如何修改它或是注入我们想要的数据,来改变清运行结果?unquote的作用正是再此。这里举一个例子
iex> denominator = 2
2
iex> quote do: divide(42, denominator)
{:divide, [], [42, {:denominator, [], Elixir}]}
iex> quote do: divide(42, unquote(denominator))
{:divide, [], [42, 2]}
除法运算参数denominator已经赋值,如果直接quote do: divide(42, denominator)其元组第三项参数项里却出现了原子:denominator,显然我们希望它一个具体的结果也就是2而不是符号denominator。所以unquote(denominator)的加入得到了我们想要的结果。其实在这里quote用来得到函数的语法树(与def的作用类似),quote的do block中一切都会被译作符号,而不计算其结果(编译时)。unquote的作用正是编译时计算函数结果填入语法树。当然这些只是elixir提供的函数功能,我们可以举一个具体例子来感受下这两个函数的实际使用(最大的作用还是在宏中,下篇博客会介绍)。假设我们需要一个月份名和月份数字的映射表,一种运行时最快的的办法就是用函数的模式匹配去映射。
defmodule Month do
mon_names=[
"Jan" , "Feb", "Mar",
"Apr", "May", "Jun",
"Jul", "Aug", "Sep",
"Oct", "Nov", "Dec" ]
Enum.reduce mon_names, 1, fn mon_name, n ->
def name2num unquote(mon_name) do
unquote(n)
end
def num2name unquote(n) do
unquote(mon_name)
end
n + 1
end
end
直接拷贝粘贴到iex中即可
appledeMacBook-Air:~ apple$ iex
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.4.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule Month do
...(1)> mon_names=[
...(1)> "Jan" , "Feb", "Mar",
...(1)> "Apr", "May", "Jun",
...(1)> "Jul", "Aug", "Sep",
...(1)> "Oct", "Nov", "Dec" ]
...(1)>
...(1)> Enum.reduce mon_names, 1, fn mon_name, n ->
...(1)> def name2num unquote(mon_name) do
...(1)> unquote(n)
...(1)> end
...(1)>
...(1)> def num2name unquote(n) do
...(1)> unquote(mon_name)
...(1)> end
...(1)>
...(1)> n + 1
...(1)> end
...(1)> end
{:module, Month,
<<70, 79, 82, 49, 0, 0, 8, 48, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 212...
iex(2)> Month.name2num "Jan"
1
iex(3)> Month.num2name 10
"Oct"
其实这种数据量较小的映射还是难以体现这样写的好处,不过如果是静态crc32的校验就能体现出这样写法的优点了(包括其他一些需要很多映射操作对时间也有要求的都可以使用,要记住elixir中函数是最快的)使用该方法虽然编译时间会加长但运行速度是最快的!附个静态CRC32的代码
defmodule CRC32 do
use Bitwise
Enum.each 0..255, fn x ->
def crc32Table unquote(x) do
unquote(
if (x &&& 1) == 1 do
(x >>> 1) ^^^ -306674912
else
x >>> 1
end)
end
end
Enum.each 0..255, fn x ->
def crc64Table unquote(x) do
unquote(
if (x &&& 1) == 1 do
(x >>> 1) ^^^ -3932672073523589310
else
x >>> 1
end)
end
end
end