I have seen macros being used in a lot of code at different parts but never really understood the thought process behind it. Can someone please list some examples with some thought process behind using macro ?
Can you add details to your question, are you interested in how macros work or what are the main use-cases?
Please give details on both
a good entry point is here Macros - The Elixir programming language, but also point 1 and 3 of the meta programming section
First thing to mention is when NOT to use a macro, that is, almost always. You only want to reach for a macro when you have no choice but to use one. See Library Guidelines — Elixir v1.15.5
There are several reasons you might need a macro, but almost all of them are for libraries, not your typical application code:
-
you need to do something at compile time, e.g. defining a function (
defn
inNx
,Plug.Router
…), which regular functions cannot do -
you want a specific DSL like
Ecto.Query
-
you want a syntax to generate a data structure that also works in patterns/guards (
../2
for ranges, Date/Time sigils like~D
…). You can doa..b = 1..10
, but functions cannot be called in patterns/guards. -
you need to access the AST, e.g.
assert/1
: if it was a function, it would only see the final value (false
), not the operation you’re trying to do (>
,==
…), the operands (left and right), or the code it is printing (code: assert 1 + 2 + 3 + 4 > 15
). -
performance/optimization:
Logger
could typically be implemented with functions, but we want to only evaluate the message if the logger level is above the configured level, and an API likeLogger.maybe_info(fn -> "hello #{world}" end)
would have a bigger runtime footprint (plus a more clunky API). Instead, we generate efficient code by checking the log level at compile time.
For an example the if/2
in Elixir is a macro that expands to a case/2
. You can see this if you disassemble the code from the BEAM file.
# macro.exs
Mix.install([{:beam_file, "~> 0.5.3"}])
defmodule Foo do
def pos(x) do
if x > 0 do
"yes"
else
"no"
end
end
end
|> BeamFile.elixir_code!()
|> IO.iodata_to_binary()
|> IO.puts()
> elixir macro.exs
defmodule Elixir.Foo do
def pos(x) do
case :erlang.>(x, 0) do
false -> "no"
true -> "yes"
end
end
end