Eiji
Please help me with macro for generating functions
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
unquoteworks inFirstExample?
unquoteshould work only inside ofquote, right? - How I could correct
SecondExampleto make it work?
Important: I need these data to be insidedefmodule unquote(name) do ... endand I need to generate whole module)?
I can see thatunquotetries to get data outside ofquote, but i don’t know how I can fix it to make it work like inFirstExample.
Marked As Solved
net
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
@gregvaughn Enum.each is fine in this case as def just inserts into an ets table.
Also Liked
mudasobwa
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
NobbZ
Appending here is totally fine. If not elixir does it, erlang will optimise it away. It will optimise appending to a singleton list into consing.
Also, there is often no problem with appending to a list once, it does get dangerous when done recursively though.
gregvaughn
I’m no macro expert, but don’t you want to be using Enum.map instead of Enum.each so you can capture the generated AST?
mudasobwa
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
mudasobwa
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.”







