marick

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

kip

ex_cldr Core Team

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_name is :fun_name and
  • 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

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

lud

No because in my snipped I wrote def unquote(name)() (with the parens for the call).

marick

marick

Wow! I would not have guessed that would work.

Last Post!

marick

marick

Wow! I would not have guessed that would work.

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New

We're in Beta

About us Mission Statement