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:
-
Even in simplest example it’s
1 LOC
vs3 LOC
where one line have extra indention. -
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
-
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?