lkuty

lkuty

Define a function at compile-time

I have a module for which I want to create a function rule_ids/0 at compile time. The code collects the rule ids at compile time from the first argument of all clauses of the function condition/3 using the AST.
It does not work because the variable rule_ids_ defined at compile time is not accessible when defining the function with def rule_ids(), do: rule_ids_.
How could I proceed to get it working? Should I use a macro somewhere?

defmodule Rules do

  # Collect the rule ids at compile time
  {_ast, rule_ids_} = File.read!(__ENV__.file)
    |> Code.string_to_quoted()
    |> Macro.traverse([],
      fn
        {:def, _, [{:condition, _, [rule_id, {:v, _, nil}, {:site, _, nil}]} | _]} = x, acc -> {x, [rule_id | acc]}
        other, acc -> {other, acc}
      end,
      fn other, acc -> {other, acc} end
    )

  # How could I use rule_ids_ here?
  def rule_ids(), do: rule_ids_

  def condition(rule_id, v, site)

  def condition(1018, v, site) do
    v[site][7018] < 50 && v[site][7019] < 50 && v[site][7020] < 50
  end

  def condition(1017, v, site) do
    v[site][7027] > 42
  end

  # ...

end

Marked As Solved

LostKobrakai

LostKobrakai

There are better ways to do this over reading “yourself” and parsing the AST.

You can use @on_definition on a module (say A) to have it call a macro or function elsewhere (Say on B) whenever a function or macro is defined on the module. With a macro being provided you can then pull information from those definitions and store them in an arbitrary module attribute of A.

You can register another callback @before_compile in A, which calls the callback macro (likely also in B) right before the module starts compiling, but after all it’s body has been evaluated. That module can then read your arbitrary module attribute for all the gathered information and turn it into AST returned from the macro. That AST is then added to the module – you can imagine it being injected right before the end line of defmodule.

Also Liked

mudasobwa

mudasobwa

Creator of Cure

While everything suggested above is absolutely correct, I’m to answer your original question.

One does not technically need module attributes, nor hooks here. The issue is scopes and we have unquote fragments for that.

rule_ids_ is defines in the outer scope for rule_ids/0 call, therefore one needs to unquote it there. THe code below would work (I also fixed the issue with condition/3 head mistakenly falling under a match during the parse stage.)

defmodule Rules do

  # Collect the rule ids at compile time
  {_ast, rule_ids_} = File.read!(__ENV__.file)
    |> Code.string_to_quoted()
    |> Macro.traverse([],
      fn
        {:def, _, [{:condition, _, [rule_id, {:v, _, nil}, {:site, _, nil}]} | _]} = x, acc 
            when not is_tuple(rule_id) ->
          {x, [rule_id | acc]}

        other, acc ->
         {other, acc}
      end,
      fn other, acc -> {other, acc} end
    )
  # print it out and see how it goes, this line is to be removed
  |> tap(& &1 |> Macro.to_string() |> IO.puts())

  # unquoting from the outer scope
  # HERE              ⇓⇓⇓⇓⇓⇓⇓                 
  def rule_ids(), do: unquote(rule_ids_)

  def condition(rule_id, v, site)

  def condition(1018, v, site) do
    v[site][7018] < 50 && v[site][7019] < 50 && v[site][7020] < 50
  end

  def condition(1017, v, site) do
    v[site][7027] > 42
  end
  # ...
end
LostKobrakai

LostKobrakai

Yeah, that’s what I was hinting at. You can remove the hardcoded Rules in the Hooks module by replacing it with env.module. Often all the boilerplate you have in Rules is also contained in a __using__ macro of Hooks, so a use Hooks does everything you need within Rules.

D4no0

D4no0

These is what module attributes are for: Module — Elixir v1.20.2

I’ve never checked it, but this should work:

  Module.register_attribute(__MODULE__, :rule_ids)

 # Collect the rule ids at compile time
  {_ast, rule_ids_} = File.read!(__ENV__.file)
    |> Code.string_to_quoted()
    |> Macro.traverse([],
      fn
        {:def, _, [{:condition, _, [rule_id, {:v, _, nil}, {:site, _, nil}]} | _]} = x, acc -> {x, [rule_id | acc]}
        other, acc -> {other, acc}
      end,
      fn other, acc -> {other, acc} end
    )
  
  Module.put_attribute(__MODULE__, :rule_ids, rule_ids_)

  # How could I use rule_ids_ here?
  def rule_ids(), do: @rule_ids

  def condition(rule_id, v, site)

  def condition(1018, v, site) do
    v[site][7018] < 50 && v[site][7019] < 50 && v[site][7020] < 50
  end

  def condition(1017, v, site) do
    v[site][7027] > 42
  end

  # ...

end

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
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
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
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
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; "XXX...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
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
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement