lakret

lakret

Getting variable by its dynamic atom name from the caller context in a macro

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?

Marked As Solved

al2o3cr

al2o3cr

The REPL is obscuring things somewhat - interpolate runs during compilation and creates an AST which is then evaluated. bar has a value during the second part of that process.

Here’s a revised version that handles the example from your post:

defmodule Foo do
  defmodule Column do
    defstruct [:name]
  end

  defmacro select(columns) do
    interpolate(columns)
  end

  def interpolate(q) do
    IO.inspect(q, label: :q)

    ast = Macro.postwalk(q, fn
      {:^, _, [{name, _, _} = v]} when is_atom(name) ->
        quote do
          atom_to_column(var!(unquote(v)))
        end
      a when is_atom(a) ->
        quote do
          atom_to_column(unquote(a))
        end
      x -> x
    end)

    IO.inspect(ast, label: :ast)

    ast
  end

  def atom_to_column(a) when is_atom(a) do
    %Column{name: a}
  end
end

Running this produces output like:

iex(32)> select([:foo, :bar])
q: [:foo, :bar]
ast: [
  {:atom_to_column, [context: Foo, import: Foo], [:foo]},
  {:atom_to_column, [context: Foo, import: Foo], [:bar]}
]
[%Foo.Column{name: :foo}, %Foo.Column{name: :bar}]
iex(33)> wat = :foobar
:foobar
iex(34)> select([:foo, ^wat])
q: [:foo, {:^, [line: 34], [{:wat, [line: 34], nil}]}]
ast: [
  {:atom_to_column, [context: Foo, import: Foo], [:foo]},
  {:atom_to_column, [context: Foo, import: Foo],
   [{:var!, [context: Foo, import: Kernel], [{:wat, [line: 34], nil}]}]}
]
[%Foo.Column{name: :foo}, %Foo.Column{name: :foobar}]

Also Liked

al2o3cr

al2o3cr

var! is a macro, so code like this:

var!(some_variable_name)

calls var! with {:some_variable_name, metadata, context}.

I found this example helpful:

defmodule MacroExample do
  defmacro get_value(variable) do
    IO.inspect(variable)
    quote do
      var!(unquote(variable))
    end
  end
end

iex(25)> require MacroExample
MacroExample
iex(26)> target_var = 42
42
iex(27)> MacroExample.get_value(target_var)
{:target_var, [line: 27], nil}
42

In your dynamic case, you likely want to capture the whole tuple and unquote it:

Macro.postwalk(quoted, fn
  {:^, [], [{v, _, _} = v_tuple]} when is_atom(v) ->
    quote do
      var!(unquote(v_tuple))
    end
  x -> x
end)

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&query=perfume&page=2, I would like to get: ...
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
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
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
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
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

Other popular topics Top

mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement