I've been working through an exercise on macros (Dave Thomas' excellent Programming Elixir 1.2, chapter 21) and I've hit a bit of a bump in my understanding of what's happening. I have two modules, Tracer
and Test
, where Tracer
redefines the def
macro to insert call and response logging as follows:
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
and Test
uses this macro to demonstrate its use:
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
When executing this, the first two functions work as expected:
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
However, the third function appears to hang - specifically it hangs at unquote(args)
:
iex(5)> Test.neg(-1)
So my question is, what causes the call to neg to hang? While I think I have an idea, I'd like to confirm (or refute) my understanding of why it does so.
The when
clause in the third function changes the representation of the quoted expression that is passed to the def
macro and I have no problems in reworking the macro to handle this. The definition
and content
passed for neg
are:
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}]}
When we reach the unquote(args)
in neg
, it is attempting to evaluate the args
expression, which I believe is a list containing a call to neg
and results in an infinite recursive loop. Is this correct? Any pointers to how I might debug/diagnose this would also be appreciated, as would links to further reading.