elixir元编程的quote与unquote

这里写图片描述
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

猜你喜欢

转载自blog.csdn.net/u011031257/article/details/80846327
今日推荐