lakret
I’m struggling with overriding hygiene during manual manipulation of AST with Macro.postwalk. In short, I want to provide a syntax for interpolating variables with ^ in a DSL, similar to Ecto. So this:
some_col_name = "foo"
...
select [:x, ^some_col_name]
should be converted internally to
%Select{
columns: ["x", "foo"]
}
There is more logic around that, though, so I need to first replace these ^some_col_name in the AST with the values of corresponding variables, and then do some more manipulation with the AST.
My attempts so far were variations on a theme:
Macro.postwalk(quoted, fn
{:^, [], [{v, [], _}]} when is_atom(v) ->
quote do
var!(unquote(v))
end
x -> x
end)
I know that I need to override hygiene, but it seems var! only works with literal variable names, and there’s no example of using dynamic atom names with it that I could find. I also tried different combinations of Macro.var, unquote, and var!, to no avail.
Any suggestions?
Trending in Questions
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
Hello,
I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
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
So my question is quite simple and i have found no conclusive answer on forum, google or AI.
Should we use :erlang.float for Integer to ...
New
Documentation
While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
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
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
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
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself.
My main conc...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #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)
al2o3cr
var!is a macro, so code like this:calls
var!with{:some_variable_name, metadata, context}.I found this example helpful:
In your dynamic case, you likely want to capture the whole tuple and unquote it:
lakret
Thanks for the help! However, that’s not the whole story, it seems
Let’s look at a more complete example:
Here we define a
Columnstruct, and a macroselectthat accepts a list of of terms, and converts those terms toColumns. We can only convert atoms toColumns, but we hope to handle interpolated variables (also containing atoms, for example) with the help ofinterpolate.Plain atom list works as expected:
Now let’s try the variable:
I understand that the problem is because we are essentially replacing
^barwithvar!({:bar, [], nil}), but what I want to do is to replace it with the value ofbar, going from^barto:foobarin the AST output ofinterpolate. I wonder if that is even possible?al2o3cr
The REPL is obscuring things somewhat -
interpolateruns during compilation and creates an AST which is then evaluated.barhas a value during the second part of that process.Here’s a revised version that handles the example from your post:
Running this produces output like:
lakret
After more trial and error I arrived at an alternative version, which seems even simpler:
And it works:
Thank you so much for the help!
al2o3cr
The alternative version works in the REPL, but so would a plain function (without the
^notation). The usual reason for choosing a macro over a function would be to do things with ASTs that can’t be done at runtime. For instance:will fail to compile with
invalid pattern in match, & is not allowed in matches.Here’s another implementation that works a little bit differently; you’d write your example as
select([:foo, bar])without the^:Since this constructs ASTs, it can be used in places where a plain function couldn’t:
One question your original post didn’t answer: what are you planning to make plain variables (no
^) do when passed toselect? Ecto.Query repurposes them to be table references.lakret
Thanks for the additional info!
There’s a bunch of other transformations that happen with those macros, for example this:
will be ultimately converted to something similar to:
avgandto_numberdon’t actually exist as a function, so using a macro is required. This is all a part of a larger framework, where you can create custom queries with syntax like:The ability to inline those
^some_varexpressions is the only thing missing for the main functionality. I was hopeful that I can isolate them into a separate function, and just operate on a AST where variables are fully replace with their values, thus simplifying other transformations (since they don’t need to care about handling variables anymore). Also note, that each of the clauses should work individually, and both in modules and in REPL.However, after more trials, investigation, and reading, I believe that this is probably not feasible - right now the whole code generation is happening at compile-time, but if I were to attempt to pre-process variable bindings beforehand, I would necessarily need to return to the caller context before continuing with my transformations.
I think it can be theoretically achivied by, for example, generating a lambda that calls itself in the caller, with a call to a macro that does post-processing; or by injecting code that uses an Agent to maintain runtime state (as in Metaprogramming Elixir’s example with HTML-DSL). But that is more complicated and harder to maintain, then handling variables in each clause, so I decided to revert back to my original solution.
The distinction between caller and macro contexts is something that I found myself rather confused about sometimes - haven’t done much metaprogramming before switching to Elixir a couple of years back, I guess that requires a bit more practice
P.S. Also, I played with
Macro.expanding Ecto queries today, and apparently they also just inline those variables as variables AST in the end, so that everything is replaced only after returning to the caller context.Also, I’m not so sure I will keep the
^notation. Probably just using plain variables will be sufficient for my use-case