Eiji

Eiji

Is evaluating a quoted expressions of nested DSL calls also considered as a bad practice?

Warning: Calling this function inside a macro is considered bad practice as it will attempt to evaluate runtime values at compile time. Macro arguments are typically transformed by unquoting them into the returned quoted expressions (instead of evaluated).

Source: Code.eval_quoted/3

I’m not sure if I understand it fully. Is it only about “runtime values” (like variables)? Does it apply also to do: block last argument for nested DSL?

For simplicity let’s assume that you start with a simplest DSL, but with 1 extra nested DSL. You prefer do … end way because it’s more flexible, so for example you can use a for … do … end special form to generate nested DSL calls based on some input. Consider below example:

defmodule MyLib do
  defmacro first_case_root(do: block) do
    quote do
      unquote(block)
    end
  end

  defmacro second_case_root(do: block) do
    Code.eval_quoted(block, [], __CALLER__)
  end

  defmacro nested do
    nil
  end
end

defmodule MyApp do
  import MyLib

  first_case_root do
    nested()
  end

  second_case_root do
    nested()
  end
end

In first case the nested macro is called when unquoting a result of the parent macro. For simplicity let’s say it’s called between first_case_root and second_case_root. This way looks a bit complicated as to store accumulated arguments we have to work in inner macro (inside quote) and/or on @before_compile.

However I found something obvious … I can just evaluate quoted block (as shown in second case). This way the nested DSL is evaluated inside a parent macro which in fact flattens nested DSL calls. This allows to work on the parent macro logic inside … a parent macro.

Now let’s go back to the quote. I understand why AST of stuff like variables should not be evaluated especially because in many macros it’s completely not important. However in this case “flattening” nested DSL calls allows to simplify code a lot:

  1. Even in simplest example it’s 1 LOC vs 3 LOC where one line have extra indention.

  2. There is no need to work on module attributes to store compile-time data. Instead we can use many if not all in-memory solutions. The simplest one is most probably Process.put/2

  3. Depending on use case the nested DSL macros does not have to return any quoted expressions and the root DSL macro returns a minimal quoted expression without storing and manipulating data

{nil, []} = Code.eval_quoted(block_with_nested_dsl_calls, [], __CALLER__)

Both macro code and the code returned by macro are much simpler. Simply imagine that a root macro with dozens or even more nested macro calls returns a minimal code which you print like tap(& &1 |> Macro.to_string() |> IO.puts()) and the output contains only desired generated code (generated functions) without lots of other lines you have to scroll in console.

Does described case is also seen as a bad practice? Or as written above the warning was intended only for “runtime values” like variables? If this is bad idea could you please explain why? Are there any side-effects of doing so?

Marked As Solved

D4no0

D4no0

I mean that is just a generic warning, you can do whatever you want in your macros.

I personally would say that using Code.eval_quoted/3 inside of your macros is an imperative way of doing things and composing the AST is the declarative way, as you are composing a data structure that is later evaluated.

I don’t have experience doing this kind of shenanigans, however I would suspect you running into trouble with evaluation contexts sooner or later when you are doing this kind of intermediary evaluations of code.

Where Next?

Popular in Questions Top

bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
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
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
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
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
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
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
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

We're in Beta

About us Mission Statement