myronmarston

myronmarston

How to access module attributes in a macro outside `quote`?

In a macro, can you access a module attribute outside quote?. I know about Module.get_attributes/2 but I can only get it to work inside quote. Outside quote, it always returns nil whenever I have tried.

Here’s an example:

defmodule MyMacros do
  defmacro my_macro() do
    module = __CALLER__.module
    Module.get_attribute(module, :foo) |> IO.inspect(label: "outside quote")

    quote do
      Module.get_attribute(unquote(module), :foo) |> IO.inspect(label: "with unquoted module")
      Module.get_attribute(__MODULE__, :foo) |> IO.inspect(label: "with __MODULE__")
    end
  end
end

defmodule UseMacros do
  require MyMacros

  @foo bar: 1
  MyMacros.my_macro()
end

(On a side note: I formatted this with normal indentation, but the forum seems to render it incorrectly for some reason…)

Here’s the output this produces:

outside quote: nil
with unquoted module: [bar: 1]
with __MODULE__: [bar: 1]

This demonstrates that __CALLER__.module is correctly getting the module, but when I try to use it outside quote to get the module attribute, I get back nil.

Any ideas why or if there’s any way to get this to work?

Most Liked

josevalim

josevalim

Creator of Elixir

This may be the source of confusion. Code in Elixir is not evaluated (executed line by line) except on IEx. It is always compiled and then executed. This means that, if you have three lines:

foo_macro()
bar_macro()
baz_macro()

First we expand foo_macro(), then we expand bar_macro(), and then baz_macro() and just then we execute their contents. This means that @attr :bar won’t be seen inside any macro because it has not been executed yet.

OvermindDL1

OvermindDL1

Because code inside a quote is not run, the AST of the code is returned instead:

iex> quote do
...> blah bleh dorp
...> end
{:blah, [], [{:bleh, [], [{:dorp, [], Elixir}]}]}

So when your module returns the quote’s return, it is just returning AST, which is then just put in place in the module at the location of the module call, it is not executed until runtime as it is just code at this point in AST form. :slight_smile:

Teeeechnically the IO.puts/1 there is put into the module’s AST, it gets executed at ‘run-time’, but the run time in this case is the module definition call, the defmodule/2 call at the top is actually a macro (in essence) that takes the body of the do as an AST and passes it to the elixir module compilation function, that function runs over the do body of the module definition and executes each expression in turn, so IO.puts/1 will print at this point, a def will call the function definition function that actually creates the function to be run (passing that function the def’s do block as an AST), etc… :slight_smile:

Remember that mix compile has a runtime of its own, and that is where the module and function and such definitions are actually executed. It is a bit more defined then how I represented it above, but that is the gist of it.

Discourse is standard markdown, so use code-fences:
```elixir
Properly formatted elixir code here
```
Turns in to:

Properly formatted elixir code here

But it did not work because your Module.put_attribute(__MODULE__, :foo, bar: 1) line is being set as a call into the already compiled module AST rather than being run before your macro is run because put_attribute/3 is a def, not a defmacro, if it were a defmacro then it would run as you expect:

:slight_smile:

Macro’s are a black art, no matter the language you use, they require that you understand not only the language well, but also ‘how’ it is compiled in pretty good detail. ^.^

OvermindDL1

OvermindDL1

The attributes do not really ‘exist’ at this time yet unless they are registered, thus what you could do is change require to use at the call site, and make a __using__ macro in your module that register_attribute/3’s the names that are needed, then in your my_macro/0 you can use Module.get_attribute(__CALLER__.module, :blah_attribute_name) or so to get the value, I think. :slight_smile:

I think that put_attribute/3 might be required though, but you can wrap that up in another macro too if needed.

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
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> somethi...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30877 112
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New

We're in Beta

About us Mission Statement