myronmarston
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?
Trending in Questions
Hey guys,
I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly
Do you guys have any suggestions what is the best prac...
New
Hello!
Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app.
I creat...
New
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
Anyone here using Honeybadger?
My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of
Bandit.HTTPError...
New
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
Other Trending Topics
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
There are three potential reasons for members of this forum to have a look at https://vutuv.de
You are tired or annoyed of LinkedIn.
Yo...
New
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
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 8- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
OvermindDL1
The attributes do not really ‘exist’ at this time yet unless they are registered, thus what you could do is change
requiretouseat the call site, and make a__using__macro in your module thatregister_attribute/3’s the names that are needed, then in yourmy_macro/0you can useModule.get_attribute(__CALLER__.module, :blah_attribute_name)or so to get the value, I think.I think that
put_attribute/3might be required though, but you can wrap that up in another macro too if needed.myronmarston
This does not make sense to me. The docs about
register_attribute/3say nothing about making the attributes exist; it just provides a way to set theaccumulateandpersistoptions from my reading. If you’re not wanting to change those options for a module attribute, it doesn’t seem likeModule.register_attribute/3does anything. Also, why doesquotemake the module attribute “exist” when it did not previously? I thought the module attribute existed as soon as it was declared.Anyhow, I updated my example to use
Module.register_atttribute/3:…and it produces the exact same result.
I tried that and I get the same result.
OvermindDL1
It doesn’t, rather putting it in a quote moves the actual attribute lookup from compile-time to run-time.
Yep, instead of
@foo bar: 1you needModule.put_attribute(__MODULE__, :bar, 1), or wrap that up in a macro too to make it shorter and easier.Really? It works here:
Results in:
myronmarston
That’s a useful distinction, but I don’t understand why code outside the
quoteis evaluated at compile-time while code inside it is evaluated at runtime.For example, consider this module:
According to my understanding, this code is evaluated at compile time, not run time. In fact, if I run
mix compileon it, it printsMyModule: 3confirming that it is evaluated as part of compilation (which is what “compile time” is, right?). If I move that code into a macro outside aquote, it produces the same result:Again, when I run
mix compile, it printsMyModule: 3, confirming the code is evaluating at compile time.So in what sense is code in a macro outside of a
quotenot evaluated at compile time, given it evaluates as part of compilation?That’s interesting your example worked. Here’s what I had tried:
(For some reason, I can’t get this to format how I want: I put the quote and my reply in separate paragraphs but the forum is collapsing it into the prior paragraph. Any idea why?)
Basically, just replacing
@foo bar: 1withModule.put_attribute(__MODULE__, :foo, bar: 1).I need to play around with your some more to see why it works while mine doesn’t.
OvermindDL1
Because code inside a quote is not run, the AST of the code is returned instead:
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.
Teeeechnically the
IO.puts/1there 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, thedefmodule/2call 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 thedobody of the module definition and executes each expression in turn, soIO.puts/1will print at this point, adefwill call the function definition function that actually creates the function to be run (passing that function thedef’sdoblock as an AST), etc…Remember that
mix compilehas 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:
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 becauseput_attribute/3is adef, not adefmacro, if it were adefmacrothen it would run as you expect: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. ^.^
myronmarston
I’m aware of that. Conceptually, code in a
quotein a macro gets injected into the call site, as I understand it. But code in a module definition is evaluated as part of compilation, it’s not clear to me why code injected into a module definition from a macroquoteis evaluated at “runtime” when it is clearly evaluated as part of the compilation process.If that’s true, then how does this work?
This dynamically defines a
Foo.foo/0function and aFoo.bar/0function. From what you’ve said, theEnum.each/2is not evaluated at compile time, running instead at runtime. But within the enumeration we are defining functions. So apparently functions are being defined and compiled at runtime instead of compile time? If module function compilation happens outside of “compile time” then what does “compile time” as a concept even mean?Does the distinction you’re making have to do with macro expansion phases? That makes more sense to me then compile time vs runtime (given you can use “runtime” code to define functions for compilation, the distinction doesn’t make sense to me at all), and it’s something I’ve seen referenced in some Elixir books and documentation.
Yep. I’ve been doing that from the start (as it’s what I’m used to from years of doing it on GitHub). Still not rendering correctly for me, though :(.
So, translating this into the concept of macro expansion phases, it sounds like my macro is being expanded during an expansion phase that happens before whatever
@attribute valueexpands into is evaluated (or beforeput_attribute/3is evaluated, if I call that directly). Is that accurate?@josevalim I think there’s a bit of a whole in the documentation regarding this stuff. I’ve read @chrismccord’s Metaprogramming Elixir and the Elixir docs regarding macros and
quoteand I don’t really think it provides the necessary concepts to understand this kind of issue (at least not for how my brain thinks, apparently). Are there some simple ways we can improve the docs regarding this? I’m thinking maybe a doc explaining macro expansion phases in more detail and how evaluation order may not match what you’d expect. And also maybe the docs on theModuleattribute functions could mention this gotcha. It definitely surprised me.OvermindDL1
Because that code itself is not executed when the AST is generated, rather it is executed after the AST is generated (the
Enum.eachcall and all are in that AST, not run yet), then passed to thedefmoduleinternal call that generates the module, it goes over the AST and anything it does not understand (special forms like the internal ‘def’ type) it executes and takes the result of like a macro, this is the step at which a ‘function’ (not a macro) call gets executed during module definition. The Enum.each returns the new ast (since the def part became ast at that point in a way) and the module compiler then uses ‘that’.Yes it is convoluted, macros often are. ^.^
Its not compiled at the ‘mix’ compile time correct, it is compiled at the ‘mix’ runtime, which is the time that mix runs all its code to do things like compiling a module to a beam file.
If you want to use program runtime (not mix runtime) code to generate a module you have to use the compile function and pass the AST itself to it. Or eval text too. ^.^
Huh, if you have a reproducable example you should send it to the meta discourse forum (the discourse forum that is hosted in discourse on the discourse website), they hop on bugs like that! ^.^
Basically, since the put_attribute was in the module area but put_attribute is a function, not a module, it got executed when the module got compiled, not when the module AST was generated, so it was too late for your macros (that are executed at ast generation (in essence, there are things about this too)) to see the result of its call.
I have no doubt they would love good documentation on this PR’s into Elixir! Macro’s are a hard hard topic, I figured out most of Elixir’s by reading the erlang source of Elixir itself, and even then there are so many different cases it is still hard to hold it all in my head (as is common in macro systems). ^.^
josevalim
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:
First we expand
foo_macro(), then we expandbar_macro(), and thenbaz_macro()and just then we execute their contents. This means that@attr :barwon’t be seen inside any macro because it has not been executed yet.