sodapopcan
Defining functions in a loop inside a macro
I can dynamically define functions in a “loop” like this:
defmodule Foo do
for {f, i} <- [{:foo, "foo"}, {:bar, "bar"}] do
def foo(unquote(f)) do
unquote(i)
end
end
end
iex> Foo.foo(:foo)
"foo"
iex> Foo.foo(:bar)
"bar"
iex> Foo.foo(:baz)
💣
So that’s all cool ![]()
Now how do I do this inside of a macro? I’ve been at this for a few hours now and having a very hard time wrapping my head around it. The closest I’ve come is this:
defmodule Foo do
defmacro __using__(_) do
quote do
for {f, i} <- [{:foo, "foo"}, {:bar, "bar"}] do
defmacro foo(f) do
quote do
# unquote(i) <- This doesn't work so I'm commenting it out for now
end
end
end
end
end
end
This has the effect of defining def foo(:foo) twice, even though I dbg(f) in inside the inner macro and it has the correct values. On top of that, I can’t access i inside the inner macro.
I got a bit of hint from this thread. I tried re-quoting that whole comprehension, capturing it in a variable, then quote(do: unquote_splicing(block)) it, but that didn’t work. There is more code that follows it so, I dunno.
I appreciate any help though if this serves as a rubber duck that’d be good too ![]()
Marked As Solved
kip
When you have nested quote blocks, you need to give Elixir a hint as to what an unuquote applies to. You do this by passing quote unquote: false to the outer block. The following compiles, but I haven’t tested if its actually what you want:
iex(1)> defmodule Foo do
...(1)> defmacro __using__(_) do
...(1)> quote unquote: false do
...(1)> for {f, i} <- [{:foo, "foo"}, {:bar, "bar"}] do
...(1)> defmacro foo(f) do
...(1)> quote do
...(1)> unquote(i)
...(1)> end
...(1)> end
...(1)> end
...(1)> end
...(1)> end
...(1)> end
{:module, Foo,
<<70, 79, 82, 49, 0, 0, 6, 168, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 177,
0, 0, 0, 17, 10, 69, 108, 105, 120, 105, 114, 46, 70, 111, 111, 8, 95, 95,
105, 110, 102, 111, 95, 95, 10, 97, 116, ...>>, {:__using__, 1}}
Also Liked
christhekeele
It’s worth remembering that the return value of a macro just needs to be a valid AST data structure to be inserted somewhere else, which can be composed however you like, including raw literals! It’s easy to forget sometimes, because quote is so powerful, and I often fall into this block-based thinking where the final expression of a defmacro has to be a top-level quote block.
However, an equally valid return value is a list of AST snippets, which will get interpreted as a series of sequential expressions. So you can just tuck the quote inside the for, and return a list of quoted expressions:
defmodule Foo do
defmacro __using__(_opts \\ []) do
for {function_argument, function_return} <- [{:fizz, "fizz"}, {:buzz, "buzz"}] do
quote do
def baz(unquote(function_argument)) do
unquote(function_return)
end
end
end
end
end
defmodule Bar do
use Foo
end
Bar.baz(:fizz)
#=> "fizz"
sodapopcan
I am quickly realizing this
I think I’m going to explore other options.
EDIT: Ya, re-reading the docs they are consolidated after everything else is compiled, so what I want to do is not possible. I have a runtime version of it working so I will probably just stick with that for now.
Thanks again for your help!
sodapopcan
Whoa! With a slight tweak that worked! Thanks so much, Kip! Though now I’m puzzled as to how it works with protocol impls, though the docs do qualify that they are usually consolidated after the app is compiled. I’m guessing if there is a compile-time dependency on it that will make it happen earlier. That is maybe supported by this but I haven’t taken the time to grok it yet.
Thanks again!
defmacro __using__(_) do
func =
quote unquote: false do
{_, mods} = Proto.__protocol__(:impls)
for mod <- mods do
type = mod.__struct__
name = Proto.name(type)
ast = Proto.body(type)
def func(unquote(name)) do
unquote(ast)
end
end
end
quote do
unquote(func)
# more stuff that depends on func/1
end
end
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








