rcm765
Why is code inside of `defmacro` being executed on compilation?
With the following code:
defmodule Foobar do
defmacro b(arg) do
arg
|> String.split("\n")
|> IO.inspect()
end
defmacro a() do
other(&b/1)
end
def other(thing), do: thing
end
during compilation of this module this error occurs:
** (FunctionClauseError) no function clause matching in String.split/3
The following arguments were given to String.split/3:
# 1
{:x1, [], :elixir_fn}
# 2
"\n"
# 3
[]
Attempted function clauses (showing 4 out of 4):
def split(string, %Regex{} = pattern, options) when is_binary(string) and is_list(options)
def split(string, "", options) when is_binary(string) and is_list(options)
def split(string, [], options) when is_binary(string) and is_list(options)
def split(string, pattern, options) when is_binary(string) and is_list(options)
(elixir 1.15.5) lib/string.ex:478: String.split/3
(stdlib 5.0.2) erl_eval.erl:750: :erl_eval.do_apply/7
(stdlib 5.0.2) erl_eval.erl:1026: :erl_eval.expr_list/7
(stdlib 5.0.2) erl_eval.erl:456: :erl_eval.expr/6
expanding macro: Foobar.b/1
#cell:opffgr7vdttksyult6zxtm6vlvo6paax:1: (file)
This implies that the code is being executed, but how and why?
Most Liked
kip
The first rule of macros is that the accept AST and they must return AST. The way this is typically accomplished is to wrap the code you want executed at runtime in a quote expression. For example:
defmacro b(arg) do
quote do
unquote(arg)
|> String.split("\n")
|> IO.inspect()
end
end
Note that in order to apply the arg inside the quoted expression, it needs to be unquoted.
hauleth
It is called - there other(&b/1). Macros aren’t (at least directly) available in runtime, so cannot be referenced. So what Elixir does is that it treat it the same way as fn a -> b(a) end, which mean it need to expand b(a) call. And there is where your code is evaluated.
zachallaun
There are at least two things going wrong here:
-
@hauleth is absolutely right, the
&b/1is being rewritten tofn x1 -> b(x1) end. The macrobthen expands with the argumentx1, which is{:x1, [], :elixir_fn}as AST. This is why you see the{:x1, [], :elixir_fn}being passed toString.split/3in your example. -
Even if you rewrite the body of
ato handle a non-string argument, e.g. ignore the argument completely and just return:ok, returning the anonymous functionfn x1 -> b(x1) endfrombis invalid:** (CompileError) scratch.exs: invalid quoted expression: #Function<0.1526323/1 in Foobar."MACRO-a"/1>To get things to compile,
aneeds to be rewritten to return valid AST:defmacro a do quote do Foobar.other(&Foobar.b/1) end end
I suspect, however, that this might not address the actual code that this example was based off of. What I think you’re trying to do is to defer macro expansion until runtime. This isn’t possible except by evaluating a quoted expression, but that is a whole other can of worms. If this is the case, it’s worth reconsidering the approach.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









