2016-04-14 77 views
1

我有關於跟隨marco的問題。考慮下面的代碼片段:帶宏的動態函數定義

defmodule Assertion do 

    defmacro __using__(_options) do 
    quote do 
     import unquote(__MODULE__) 
     Module.register_attribute __MODULE__, :tests, accumulate: true 
     @before_compile unquote(__MODULE__) 
    end 
    end 

    defmacro __before_compile__(_env) do 
    IO.puts "before compile" 
    quote do 
     def run, do: Assertion.Test.run(@tests, __MODULE__)  
    end 
    end 

    defmacro test(description, do: test_block) do 
    test_func = String.to_atom(description) 
    quote do 
     @tests {unquote(test_func), unquote(description)} 
     def unquote(test_func)(), do: unquote(test_block) 
    end 
    end 

    defmacro assert({operator, _, [lhs, rhs]}) do 
     IO.puts "assert" 
    quote bind_quoted: [operator: operator, lhs: lhs, rhs: rhs] do 
     Assertion.Test.assert(operator, lhs, rhs) 
    end 
    end 
end 

defmodule Assertion.Test do 
    def run(tests, module) do        
    Enum.each tests, fn {test_func, description} -> 
     case apply(module, test_func, []) do 
     :ok    -> IO.write "." 
     {:fail, reason} -> IO.puts """ 

      =============================================== 
      FAILURE: #{description} 
      =============================================== 
      #{reason} 
      """ 
     end 
    end 
    end              

    def assert(:==, lhs, rhs) when lhs == rhs do 
    :ok 
    end 
    def assert(:==, lhs, rhs) do 
    {:fail, """ 
     Expected:  #{lhs} 
     to be equal to: #{rhs} 
     """ 
    } 
    end 

    def assert(:>, lhs, rhs) when lhs > rhs do 
    :ok 
    end 
    def assert(:>, lhs, rhs) do 
    {:fail, """ 
     Expected:   #{lhs} 
     to be greater than: #{rhs} 
     """ 
    } 
    end 
end 

和下面的模塊使用宏:

defmodule MathTest do 
    use Assertion 
    test "integers can be added and subtracted" do 
    assert 2 + 3 == 5 
    assert 5 - 5 == 10 
    end 
end 

查看測試宏觀上線

def unquote(test_func)(), do: unquote(test_block) 

我這裏注入一個函數來調用模塊名稱爲integers can be added and subtracted,並在它被轉換爲原子之前。

如何有可能給空間函數命名? def unquote(test_func)之後是什麼()

回答

2

什麼quoteunquote是創建一個抽象語法樹(AST)。讓我們來看看,比較

正常功能定義

iex(1)> quote do def foo(), do: :ok end 
{:def, [context: Elixir, import: Kernel], 
[{:foo, [context: Elixir], []}, [do: :ok]]} 

我們可以看到該功能的名稱存儲在AST爲{:foo, [context: Elixir], []},那就是,這個名字被存儲爲一個原子

使用unquote

iex(2)> foovar=:'foo var'    
:"foo var" 
iex(3)> quote do def unquote(foovar)(), do: :ok end 
{:def, [context: Elixir, import: Kernel], 
[{:"foo var", [context: Elixir], []}, [do: :ok]]} 

這裏函數定義的名稱存儲在與元組的名稱部分作爲previousl的AST Ÿ定義的變量,:"foo var"

你可以使用內置的功能apply


()是可選的調用這些類的功能。包括我自己在內的一些人爲了一致性,更喜歡在所有函數定義和函數調用中使用它們。


注:

一個我喜歡的二郎語法更好的事情是,函數名和原子看起來是一樣的,而且它們的排序是相同的。這就是你可以說Module.foo只是糖apply(Module, :foo, [])。 elixir函數名稱看起來像變量,它與可選的括號可以使代碼更難以快速閱讀和理解。