Hi, I looked about generating functions and found one example. I tried to make it work inside macro, but i don’t know how I can fix it. Here is sample code that I need help with:
defmodule FirstExample do
Enum.each [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
def unquote(func_name)(unquote_splicing(args)), do: :ok
end
end
defmodule SecondExample do
defmacro my_macro(name) do
quote do
defmodule unquote(name) do
Enum.each [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
def unquote(func_name)(unquote_splicing(args)), do: :ok
end
end
end
end
end
Please explain me:
How unquote works in FirstExample? unquote should work only inside of quote, right?
How I could correct SecondExample to make it work? Important: I need these data to be inside defmodule unquote(name) do ... end and I need to generate whole module)?
I can see that unquote tries to get data outside of quote, but i don’t know how I can fix it to make it work like in FirstExample.
Use Module.create/3 that, as by documentation, “Creates a module with the given name and defined by the given quoted expressions.”
defmodule SecondExample do
defmacro my_macro(name) do
contents =
quote do
Enum.map [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
quote do: def unquote(func_name)(unquote_splicing(args)), do: :ok
end
end
quote bind_quoted: [name: name, contents: contents] do
Module.create(name, contents, Macro.Env.location(__ENV__))
end
end
end
defmodule Test do
require SecondExample
def test, do: IO.inspect SecondExample.my_macro(Foo), label: "Module content"
end
Test.test
@mudasobwa: Thank you. It’s really helpful, but I still can’t get it work with my code
I have 3 problems:
If I place 3 times similar each call (with another function names) then only last is added as module body (I can’t run other functions in iex). I want to pass whole module body there - not only last result of quote.
After change order nothing happens until I remove _build folder and compile whole project again - this is really uncomfortable.
In my case there is another problem: I have stored list of that data (function names and arguments) in module attribute and i would like to keep them there.
The workaround for 1st problem:
# ...
contents = quote do
[quote do
# first part, define and work on module attributes and functions
end]
++
Enum.map(...) # generated functions here ...
end
# ...
but I think it’s not a best idea.
I will try to describe better how my code works. In my version these looks like:
defmodule MyLib do
defmacro my_macro(name, do: block) do
quote do
defmodule unquote(name) do
# alias, import and require here ...
# pre work: attributes + define some methods
_ = unquote(block)
# post work: also attributes + define some methods
# call macro that returns this list - IO.inspect here shows correct data,
# but here starts my problem, because I can't use unquote here
# and I can't use unquote_splicing if I try to place `each` call in separate macro
# it's something like:
result_of_my_macro = Example.parse_data(@my_data)
Enum.each result_of_macro, fn {func_name, args} ->
def unquote(func_name)(unquote_splicing(args)), do: :ok
end
# unfortunately I can't use your version (it looks really good),
# because I have data for Enum.each inside module body
# i.e. in same scope as definition of that functions
end
end
end
end
# There are much more, but I hope that you now better understand my problem
# in another file:
require MyLib
MyLib.my_macro ModuleName do
# here I use some macro dynamically imported into this module body
end
Don’t prepend the element to the list with ++, use [head | tail] notation:
contents = quote do
[
quote do
# first part, define and work on module attributes and functions
end |
Enum.map(...) # generated functions here ...
]
end
You seem to misheard what I was saying: one should not use defmodule inside macros/functions due to the unquoting scopes problems you’ve met. Use Module.create/3 in such a case. There is no way (at least legit) to unquote from the middle level of nested quoting.
Whether you are still positive you want to violate guidelines and how-tos provided by core team members, you might use a hack (read: a kludge) with Code.eval_quoted/3 as shown below:
defmodule KludgeExample do
defmacro my_macro(name) do
quote do
defmodule unquote(name) do
Enum.each [foo: [{:_arg1, [], nil}], bar: []], fn {func_name, args} ->
ast = quote do: def unquote(func_name)(unquote_splicing(args)), do: :ok
Code.eval_quoted(ast, [func_name: func_name, args: args], __ENV__)
end
end
end
end
end
defmodule Test do
require KludgeExample
def test do
KludgeExample.my_macro(Foo)
IO.inspect {Foo.foo(42), Foo.bar}, label: "{Foo.foo(42), Foo.bar()}"
end
end
Test.test
In my case there is another problem: I have stored list of that data (function names and arguments) in module attribute and i would like to keep them there.
I do not see any problem here: add the module attribute declaration to the contents as shown below:
contents =
quote do
[ quote do
Module.put_attribute(__MODULE__, :my_data, ...)
# first part, define and work on module attributes and functions
end |
Enum.map(...) # generated functions here ...
]
end
Yes, indeed, I should’ve put the better wording there. I meant “get a habit to use [head | tail] notation, rather than [head] ++ tail, it might save you time for not thinking whether it’s a performance hit or not.”
I’m guessing @my_data is set by the block. You can split large quote into two quotes, and use [unquote: false] to disable unquote/1. Consider moving the first quote to a private function in MyLib for better readability and maintainability.
defmodule MyLib do
defmacro my_macro(name, do: block) do
function_generator =
quote unquote: false do
for {name, args} <- Example.parse(@my_data) do
def unquote(name)(unquote_splicing(args)), do: :ok
end
end
quote do
defmodule unquote(name) do
unquote(block)
unquote(function_generator)
end
end
end
end
@gregvaughnEnum.each is fine in this case as def just inserts into an ets table.
@mudasobwa: Thanks, but these does not look good (or another words: these could look better), but your example is very, very good for simpler cases (your first example looks really nice).
I know [head | tail] notation, but this was only example - i.e. at end I will rewrite it anyway, but I don’t know about what @NobbZ say - really interesting, thanks!
You should agree with me that @net solution looks best for my case - i.e. there is no need to care about what quoted block returns which was exactly what I want. Just now I tested it with my code - it’s really little change in this case and it works as expected. For me it looks best, so I mark it as a answer.