matteosister

matteosister

Creating a variable with a macro, and then use it in its body

I’m trying to do something similar to wath ecto does when defining schemas. Basically the idea is:

defmodule Test do
  use TableBuilder

  row "test" do
    field "field1"
    field "field2"
  end
end

row and field are macros defined in the TableBuilder module, what I’m trying to achieve is a DSL that underneath builds a Row struct, that has a fields collection of Field structs

here is what I’m doing right now:

defmodule TableBuilder do
  defmacro __using__(_opts) do
    quote do
      import unquote(__MODULE__)
      Module.register_attribute __MODULE__, :rows, accumulate: true
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      def get_table do
        inspect @rows
      end
    end
  end

  defmacro row(name, do: fields) do
    row = %Row{name: name}
    quote do
      @rows {unquote(row), unquote(fields)}
    end
  end

  defmacro field(name) do
    quote do
      %Field{name: unquote(name)}
    end
  end
end

the problem is:

when the field macro is called, I need to access the row variable created in the row macro.
But I can’t find a solution.

What’s the problem with this approach? Am I doing it wrong? Do you see better approaches to the problem?

Many thanks in advance!

Most Liked

Qqwy

Qqwy

TypeCheck Core Team

By default, Macros in Elixir are unable to access variables outside of their definition. This is called ‘hygienic’. It is also possible to create unhygienic macros, which are what you are looking for here. Saša Jurić’s guide on Macros has some great information on this subject.

matteosister

matteosister

yes! And this is exactly what I’m doing right now.

But this approach do not work perfectly for me for two reasons:

  • I need to define multiple rows in a single module (and right now I’m using the fields attribute and then wipe it right after the macro body invocation to be empty for the next row)

  • the fact that you are not forced in any way to use field inside a row macro, so weird things could happen. But maybe this is also a problem for ecto…and with a good documentation should be ok.

I’d really love to create a struct in the row macro and than pass it (like var!) when unquoting the body, but as far as I know it’s impossible…

matteosister

matteosister

thanks for pointing me there…after many attempts I found a nice solution. Not so clean, but it’s working. :smiley:

Qqwy

Qqwy

TypeCheck Core Team

I looked through the Schema source code of Ecto yesterday and they seem to use Module.set-attribute/1 and Module.get_attribute/1 to ‘cheat’ the variable-passing at compile-time. Roughly the following happens:

  1. the schema macro is called.
  2. amongst other things, astruct_fields module attribute with append: trueis instantiated.
  3. the do block passed to the macro is evaluated.
  4. each field, has_one, has_manyadds the specified field to the struct_fieldsmodule attribute
  5. back after the do-evaluation, the struct_fields are read and a struct is defined with them.

Where Next?

Popular in Questions Top

yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
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
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
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
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement