Nah, it’s just practice. ^.^
Just remember, if a function is defined as defmacro
(p
) then it gets passed the AST of the arguments and should return an AST. If a function is defined as def
(p
) then it is executed at run-time, I.E. in the run context of where it is called instead of when its context is defined.
Now given that, you call your my_macro/2
, it is a defmacro
so it is executed at the build-time of the context it is called from. Inside of it you define a binding named sender
of that string, that line is executed in the run context of my_macro/2
inside the MacroTest
module. The next expression, the quote
creates a new context inside of it, the code you define is not doing anything, not calling macro’s, not binding anything, it does nothing beyond just define the appropriate AST. The quote
call overrides a few special things, like unquote/1
and var!/1
. The unquote/1
just runs whatever code is inside of it and injects it into the AST at its point, so doing this:
iex(1)> a = Macro.var(:blah, __MODULE__)
{:blah, [], nil}
iex(2)> quote(do: unquote(a) = 42)
{:=, [], [{:blah, [], nil}, 42]}
Which is entirely identical to:
iex(3)> a = Macro.var(:blah, __MODULE__)
{:blah, [], nil}
iex(4)> {:=, [], [a, 42]}
{:=, [], [{:blah, [], nil}, 42]}
It is just like putting the variable directly into the AST itself, just inside of a quote context to make it easier.
Now, unquote/1
is a special form, it just *can*not* be called out of a quoted context, and in fact you get the error of (CompileError) unquote called outside quote
, however var!/1
is just a macro, all it does is resolve the given variable and return as in:
iex(5)> a = Macro.var(:blah, __MODULE__)
{:blah, [], nil}
iex(6)> var!(a)
{:blah, [], nil}
Or when in a head, like in your my_fun/3
, it returns the binding, so it is the same as not having it there at all (I’m not actually sure why you put var!/1
in the arguments at all, unsure what that was supposed to do). In essence, it basically does nothing most of the time, however it is useful at times to generate AST, so it is useful in, say, a quote (which will end up just returning the given variable verbatim with no context). Now do note, a binding in the ast is just {name, meta, context}
, the name is an atom, the meta is a keyword list of meta information, like line number, the empty list of []
is fine, and the context is an atom. The name is obviously the name, how it is referred to. The context is just anything to define a ‘uniqueness’ to the name, so a binding of {:blah, [], nil}
and {:blah, [], Blah}
are two different bindings, even though they have the same name, they are two different bindings. A variable in a quote is always given a default context, so doing this:
iex(7)> quote(do: blah = 42)
{:=, [], [{:blah, [], Elixir}, 42]}
As you can see it gave it a context, Elixir
in this case since I was in iex, but usually your module, that just makes it so your variables do not accidentally stomp all over the variables in where-ever you are injecting this AST in to, var!/1
is just a convenient way to refer to some variable in the global context of where this is going to be injected, which always has a nil
context.
Now in your quote you are unquote’ing your __MODULE__
, which injects the module name ast into the ast you are building, which you then call (via .
dot) the my_fun/3
function, which will have 3 arguments, each of which are unquoted in to the ast. In your second post you have examples like MacroTest.my_macro(:printer, "hi")
, so ‘action’ is becoming the ast of the atom :printer
, which will actually just be :printer
nicely enough, and same of msg
with "hi"
, so that the quote expression puts out is basically MacroTest.my_fun(:printer, "hi", "from MacroTest")
, and that is injected into the context that is being built in where-ever my_macro/2
is being called from, so it will then process that just like normal ast, it sees that my_fun/3
is not a macro so it leaves the call in the ast and stores it for the run-time phase later (after compiling and so forth).
Later when the context that called my_macro/2
is ‘run’ (or ‘now’ if in iex for example), then my_fun/3
will be called as above. What happens is that my_fun calls unquote(MacroTest.run_action(action))
, and it will die horrible with the above error message if unquote gets called, so no matter what happens here it dies.
Just remember the two contexts.
For note, you probably want to do something like:
defmodule MacroTest do
defmacro my_macro(action, msg) do
sender = " from #{__MODULE__}"
quote do
MyOtherModule.unquote(action)(unquote(msg), unquote(sender))
end
end
end
That will have your examples above work.
I’ve no clue about the instrument part in your latest message, I’ve never actually used phoenix’s instrumentation (I’m used to erlang’s lower level facilities) so unsure what needs to be what or how. If you could give a full example of the test of what you want the api to be and how it should work to test then I’m sure someone here could help.