marick
Macros that define functions: conversions between an atom and an AST leaf that is an atom
Sometimes an argument to a macro is to be used as the name of a function being created with def. That’s straightforward[*].
But suppose the name of the function is also to be used as an atom within the body of a function. (Think of using the name to look up a value in the application environment.) Is there a better way to do that than what I show below?
Example 1: config2 from_config2
The config2 macro is to define a zero-argument function from_config2 that returns :from_config2. The following implementation works:
defmacro config2(quoted_name) do
# quoted_name is this tuple: `{:from_config_2, [line: 75], nil}`
name_atom = elem(quoted_name, 0)
quote do
def unquote(quoted_name), do: unquote(name_atom)
end
end
Plucking a value from a tuple (line 3) seems rather slimy. Is there a better way?
Example 2: config3 :from_config3
This is the same as the above, except that the macro argument is an explicit atom (and thus not wrapped in a tuple). So I can do the wrapping, as in line 3 below:
defmacro config3(name_atom) do
# name_atom is just `:from_config_3`
quoted_name = {name_atom, [], nil}
quote do
def unquote(quoted_name), do: unquote(name_atom)
end
end
Is there a better way?
[*] If you haven’t done it before, defining a function in a macro looks something like this:
defmacro defchain(head, do: body) do
quote do
def unquote(head) do
_called_for_side_effect = unquote(body)
unquote(value_arg(head))
end
end
end
One note: guards make a function definition’s syntax tree a little weird. So if you want to write macros that handle this:
defchain assert_field(kvs, list) when is_list(list) do
assert_fields(kvs, list)
end
… you might have to do some processing like this. That’s the definition of value_arg as used above.
Marked As Solved
kip
Given that you are looking to define a function call, and to also extract the atom that is the name of the function being called then pattern matching would be more idiomatic but not materially different to using Kernel.elem/2.
Given that that AST:
- for
:fun_nameis:fun_nameand - for
fun_name()is{:fun_name, [], [])
Then
{fun_name_as_atom, _meta, _params} = {:fun_name, [], []) would seem to be the idiomatic way to go.
Using your example:
defmacro config2(quoted_name) do
# quoted_name is this tuple: `{:from_config_2, [line: 75], nil}`
{name_atom, _meta, _args} = quoted_name
quote do
def unquote(quoted_name), do: unquote(name_atom)
end
end
Also Liked
lud
Your examples are fine.
This may be shorter, but may not be better:
defmacro config2({name, _, []}) do
quote do
def unquote(name)(), do: unquote(name)
end
end
lud
marick
Last Post!
marick
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
- #code-sync
- #javascript
- #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
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









