2016-10-10 47 views
2

我一直在通過宏(Dave Thomas的優秀Programming Elixir 1.2,第21章)的練習,並且在理解發生的事情方面我遇到了一些問題。我有兩個模塊,TracerTest,其中Tracer重新定義了def宏將調用和響應記錄如下:在宏定義中不引用參數會掛起函數調用

defmodule Tracer do 
    def dump_args(args) do 
    args |> Enum.map(&inspect/1) |> Enum.join(",") 
    end 

    def dump_defn(name, args) do 
    "#{name}(#{dump_args(args)})" 
    end 

    defmacro def({name, _, args} = definition, do: content) do 
    IO.puts("Definition: #{inspect definition}") 
    IO.puts("Content: #{inspect content}") 
    quote do 
     Kernel.def unquote(definition) do 
     IO.puts("==> call: #{Tracer.dump_defn(unquote(name), unquote(args))}") 
     result = unquote(content) 
     IO.puts("<== resp: #{inspect result}") 
     result 
     end 
    end 
    end 
end 

Test使用該宏來說明其使用:

defmodule Test do 
    import Kernel, except: [def: 2] 
    import Tracer, only: [def: 2] 

    def puts_sum_three(a, b, c), do: IO.inspect(a+b+c) 
    def add_list(list), do: Enum.reduce(list, 0, &(&1 + &2)) 
    def neg(a) when a < 0, do: -a 
end 

當執行這,前兩個功能按預期工作:

iex(3)> Test.puts_sum_three(1,2,3) 
==> call: puts_sum_three(1,2,3) 
6 
<== resp: 6 
6 
iex(4)> Test.add_list([1,2,3,4]) 
==> call: add_list([1, 2, 3, 4]) 
<== resp: 10 
10 

但是,第三個函數似乎掛起 - 特別是它掛在unquote(args)

iex(5)> Test.neg(-1) 

所以我的問題是,是什麼原因導致調用NEG掛?雖然我想我有一個想法,但我想確認(或駁斥)我爲什麼這樣做的理解。

第三個函數中的when子句更改傳遞給def宏的帶引號表達式的表示形式,並且在重新處理宏以處理此操作時沒有問題。通過對negdefinitioncontent是:

Definition: {:when, [line: 7], [{:neg, [line: 7], [{:a, [line: 7], nil}]}, {:<, [line: 7], [{:a, [line: 7], nil}, 0]}]} 
Content: {:-, [line: 7], [{:a, [line: 7], nil}]} 

當我們到達negunquote(args),它正試圖評估args表達,我相信這是包含在一個無限遞歸到neg和結果的通話清單循環。它是否正確?任何指向我可能調試/診斷的指針也將被讚賞,以及進一步閱讀的鏈接。

回答

3

當我們到達negunquote(args),它正試圖評估args表達,我相信這是包含在一個無限遞歸循環到neg和結果的通話清單。它是否正確?

是的,這是該行編譯爲:

IO.puts("==> call: #{Tracer.dump_defn(:when, [neg(a), a < 0])}") 

導致無限遞歸。

任何指向我如何調試/診斷的指針也將被讚賞,以及鏈接到進一步閱讀。

一種方法是將quote返回的值傳遞給|> Macro.to_string |> IO.puts。這將打印由該quote表達式生成的確切代碼。這正是我所做的,以確認你的假設:

defmacro def({name, _, args} = definition, do: content) do 
    IO.puts("Definition: #{inspect definition}") 
    IO.puts("Content: #{inspect content}") 
    ast = quote do 
    Kernel.def unquote(definition) do 
     IO.puts("==> call: #{Tracer.dump_defn(unquote(name), unquote(args))}") 
     result = unquote(content) 
     IO.puts("<== resp: #{inspect result}") 
     result 
    end 
    end 
    ast |> Macro.to_string |> IO.puts 
    ast 
end 

此打印:

Kernel.def(neg(a) when a < 0) do 
    IO.puts("==> call: #{Tracer.dump_defn(:when, [neg(a), a < 0])}") 
    result = -a 
    IO.puts("<== resp: #{inspect(result)}") 
    result 
end 

這使得它清楚爲什麼功能掛起。解決這個問題


一種方法是提取的實際名稱/ ARGS如果名稱爲:when

... 
{name, args} = 
    case {name, args} do 
    {:when, [{name, _, args}, _]} -> {name, args} 
    {name, args} -> {name, args} 
    end 
quote do 

在此之後,Test.neg/1作品:

iex(1)> Test.neg -1 
==> call: neg(-1) 
<== resp: 1 
1 
iex(2)> Test.neg -100 
==> call: neg(-100) 
<== resp: 100 
100 
    ... 
+0

謝謝你提出的解決問題的方法實際上是我如何解決這個問題,這讓我對自己開始明白髮生了什麼充滿信心! – Taufiq