Thanks for the additional info!
There’s a bunch of other transformations that happen with those macros, for example this:
select [avg(to_number(:x)), "foo"]
will be ultimately converted to something similar to:
%Select{
columns: [
%Column{
function: :avg,
arguments: [
%Column{function: :to_number, name: :x}
]
},
%Column{
value: "bar",
value_type: "string"
}
]
}
avg
and to_number
don’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:
query do
select [avg(to_number(:x)), "foo", ^some_var]
from "some_source_name"
group_by [:something_else]
having :x > 100.0
end
The ability to inline those ^some_var
expressions 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.expand
ing 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