wfgilman
I’m trying to implement a conceptual test with the following macro. I want to be able to pass an action argument which will call a function of the same name in another module with the msg macro argument as the callback function’s argument. This is the same design as the instrument/3 macro in Phoenix.Endpoint.Instrument. But I’m getting an error when I try to compile it.
Can anyone help me understand why?
Error:
warning: variable "action" does not exist and is being expanded to "action()", please use parentheses to remove the ambiguity or change the variable name
macro.ex:11
== Compilation error on file macro.ex ==
** (CompileError) macro.ex:11: undefined function action/0
(elixir) expanding macro: Kernel.def/2
macro.ex:10: MacroTest (module)
(elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
** (exit) shutdown: 1
(elixir) lib/kernel/parallel_compiler.ex:291: Kernel.ParallelCompiler.handle_failure/3
(elixir) lib/kernel/parallel_compiler.ex:247: Kernel.ParallelCompiler.wait_for_messages/1
(elixir) lib/kernel/parallel_compiler.ex:62: Kernel.ParallelCompiler.spawn_compilers/3
(iex) lib/iex/helpers.ex:170: IEx.Helpers.c/2
The code:
defmodule MacroTest do
defmacro my_macro(action, msg) do
sender = " from #{__MODULE__}"
quote do
unquote(__MODULE__).my_fun(unquote(action), unquote(msg), unquote(sender))
end
end
def my_fun(action, var!(msg), var!(sender)) do
unquote(MacroTest.run_action(action))
end
def run_action(action) do
quote do
MyOtherModule.unquote(action)(var!(msg), var!(sender))
end
end
end
defmodule MyOtherModule do
def sender(msg, sender) do
send self(), msg <> sender
end
def printer(msg, sender) do
IO.puts msg <> sender
end
end
Trending in Questions
Hello!
Suppose you are building workflow (order / task / payment) processing system with the following requirements:
Each workflow con...
New
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
Hello,
I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter).
The diffic...
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 think I’ve found a small improvement I could contribute to <%= web_namespace %>.CoreComponents (installer/templates/phx_web/compo...
New
Other Trending Topics
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
Aludel - LLM Evaluation Workbench
Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
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
- #elixir-ls
- #blog-post
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
benwilson512
I’m not entirely clear on what you’re trying to accomplish here, but you have at least 2 major difficulties with the code as is:
var!out side of a quoted context.Can you provide an example of what using this macro looks like, and what code you’re hoping the macro would produce?
wfgilman
I’d like it to work the following way:
I’m trying to replicate a conceptual example of the instrument/3 macro. I just can’t seem to reproduce it due to my lack of understanding of macros.
I thought my_fun/3 was being called in the quoted context for example, because it is being called from within the macro definition.
OvermindDL1
Hmm, let’s see…
Nope, it is not being called from the quoted context, it is being passed as a call, and since it is defined as:
So it is not a macro, so it gets stored into the AST as a call for run-time, also the rest of the head:
Uhh, does
var!even work there? I guess it might but it would be basically no-op in that position since you are naming a variable that is being defined in a head anyway, those should only ‘only’ ever be used in a quoted context. ^.^wfgilman
Okay, I can see that I have no business writing macros at my level of understanding since I can’t even follow these responses
. I was trying to write a module for testing that simulates the
instrument/3macro inPhoenix.Endpoint.Instrumentso I can test my instrumenters. The macro should produce the following code:OvermindDL1
Nah, it’s just practice. ^.^
Just remember, if a function is defined as
defmacro(p) then it gets passed the AST of the arguments and should return an AST. If a function is defined asdef(p) then it is executed at run-time, I.E. in the run context of where it is called instead of when its context is defined.Now given that, you call your
my_macro/2, it is adefmacroso it is executed at the build-time of the context it is called from. Inside of it you define a binding namedsenderof that string, that line is executed in the run context ofmy_macro/2inside theMacroTestmodule. The next expression, thequotecreates a new context inside of it, the code you define is not doing anything, not calling macro’s, not binding anything, it does nothing beyond just define the appropriate AST. Thequotecall overrides a few special things, likeunquote/1andvar!/1. Theunquote/1just runs whatever code is inside of it and injects it into the AST at its point, so doing this:Which is entirely identical to:
It is just like putting the variable directly into the AST itself, just inside of a quote context to make it easier.
Now,
unquote/1is a special form, it just *can*not* be called out of a quoted context, and in fact you get the error of(CompileError) unquote called outside quote, howevervar!/1is just a macro, all it does is resolve the given variable and return as in:Or when in a head, like in your
my_fun/3, it returns the binding, so it is the same as not having it there at all (I’m not actually sure why you putvar!/1in the arguments at all, unsure what that was supposed to do). In essence, it basically does nothing most of the time, however it is useful at times to generate AST, so it is useful in, say, a quote (which will end up just returning the given variable verbatim with no context). Now do note, a binding in the ast is just{name, meta, context}, the name is an atom, the meta is a keyword list of meta information, like line number, the empty list of[]is fine, and the context is an atom. The name is obviously the name, how it is referred to. The context is just anything to define a ‘uniqueness’ to the name, so a binding of{:blah, [], nil}and{:blah, [], Blah}are two different bindings, even though they have the same name, they are two different bindings. A variable in a quote is always given a default context, so doing this:As you can see it gave it a context,
Elixirin this case since I was in iex, but usually your module, that just makes it so your variables do not accidentally stomp all over the variables in where-ever you are injecting this AST in to,var!/1is just a convenient way to refer to some variable in the global context of where this is going to be injected, which always has anilcontext.Now in your quote you are unquote’ing your
__MODULE__, which injects the module name ast into the ast you are building, which you then call (via.dot) themy_fun/3function, which will have 3 arguments, each of which are unquoted in to the ast. In your second post you have examples likeMacroTest.my_macro(:printer, "hi"), so ‘action’ is becoming the ast of the atom:printer, which will actually just be:printernicely enough, and same ofmsgwith"hi", so that the quote expression puts out is basicallyMacroTest.my_fun(:printer, "hi", "from MacroTest"), and that is injected into the context that is being built in where-evermy_macro/2is being called from, so it will then process that just like normal ast, it sees thatmy_fun/3is not a macro so it leaves the call in the ast and stores it for the run-time phase later (after compiling and so forth).Later when the context that called
my_macro/2is ‘run’ (or ‘now’ if in iex for example), thenmy_fun/3will be called as above. What happens is that my_fun callsunquote(MacroTest.run_action(action)), and it will die horrible with the above error message if unquote gets called, so no matter what happens here it dies.Just remember the two contexts.
For note, you probably want to do something like:
That will have your examples above work.
I’ve no clue about the instrument part in your latest message, I’ve never actually used phoenix’s instrumentation (I’m used to erlang’s lower level facilities) so unsure what needs to be what or how. If you could give a full example of the test of what you want the api to be and how it should work to test then I’m sure someone here could help.
wfgilman
I wish I could give multiple likes! Thank you for the detailed response and education. It helped me solve my problem
Below is a working version of my macro. I am dropping this in my test module to simulate Phoenix’s Instrumentation API so I can test my instrumenter:
I can then use it in my test like-so:
Also helpful was @sasajuric’s series on macros. This community is awesome!