Macro
Macros are compile-time constructs that are invoked with Elixir's AST as input and a superset of Elixir's AST as output.
Let's see a simple example that shows the difference between functions and macros:
defmodule Example do defmacro macro_inspect(value) do IO.inspect(value) value end def fun_inspect(value) do IO.inpect(value) value end end
Now let's give it a try:
import Example macro_inspect(1) #=> 1 #=> 1 fun_inspect(1) #=> 1 #=> 1
So far they behave the same, as we are passing an integer as argument. But what happens when we pass an expresion:
macro_inspect(1 + 2) #=> {:+, [line: 3], [1, 2]} #=> 3 fun_inspect(1 + 2) #=> 3 #=> 3
The macro receives the representation of the code given as argument, while a function receives the result of the code given as argument. A macro must return a superset of the code representation. See input/0
and output/0
for more information.
To learn more about Elixir's AST and how to build them programmatically, see quote/2
.
Custom Sigils
Macros are also commonly used to implement custom sigils. To create a custom sigil, define a function with the name sigil_{identifier}
that takes two arguments. The first argument will be the string, the second will be a charlist containing any modifiers. If the sigil is lower case (such as sigil_x
) then the string argument will allow interpolation. If the sigil is upper case (such as sigil_X
) then the string will not be interpolated.
Valid modifiers include only lower and upper case letters. Other characters will cause a syntax error.
The module containing the custom sigil must be imported before the sigil syntax can be used.
Examples
defmodule MySigils do defmacro sigil_x(term, [?r]) do quote do unquote(term) |> String.reverse() end end defmacro sigil_x(term, _modifiers) do term end defmacro sigil_X(term, [?r]) do quote do unquote(term) |> String.reverse() end end defmacro sigil_X(term, _modifiers) do term end end import MySigils ~x(with #{"inter" <> "polation"}) #=>"with interpolation" ~x(with #{"inter" <> "polation"})r #=>"noitalopretni htiw" ~X(without #{"interpolation"}) #=>"without \#{"interpolation"}" ~X(without #{"interpolation"})r #=>"}\"noitalopretni\"{# tuohtiw"
Summary
Types
- captured_remote_function()
A captured remote function in the format of &Mod.fun/arity
- input()
The inputs of a macro
- metadata()
A keyword list of AST metadata.
- output()
The output of a macro
- t()
Abstract Syntax Tree (AST)
Functions
- camelize(string)
Converts the given string to CamelCase format.
- decompose_call(ast)
Decomposes a local or remote call into its remote part (when provided), function name and argument list.
- escape(expr, opts \\ [])
Recursively escapes a value so it can be inserted into a syntax tree.
- expand(ast, env)
Receives an AST node and expands it until it can no longer be expanded.
- expand_once(ast, env)
Receives an AST node and expands it once.
- generate_arguments(amount, context)
Generates AST nodes for a given number of required argument variables using
Macro.var/2
.- operator?(name, arity)
Returns
true
if the given name and arity is an operator.- pipe(expr, call_args, position)
Pipes
expr
into thecall_args
at the givenposition
.- postwalk(ast, fun)
Performs a depth-first, post-order traversal of quoted expressions.
- postwalk(ast, acc, fun)
Performs a depth-first, post-order traversal of quoted expressions using an accumulator.
- prewalk(ast, fun)
Performs a depth-first, pre-order traversal of quoted expressions.
- prewalk(ast, acc, fun)
Performs a depth-first, pre-order traversal of quoted expressions using an accumulator.
- quoted_literal?(term)
Returns
true
if the given quoted expression represents a quoted literal.- special_form?(name, arity)
Returns
true
if the given name and arity is a special form.- struct!(module, env)
Expands the struct given by
module
in the givenenv
.- to_string(tree, fun \\ fn _ast, string -> string end)
Converts the given expression AST to a string.
- traverse(ast, acc, pre, post)
Performs a depth-first traversal of quoted expressions using an accumulator.
- underscore(atom)
Converts the given atom or binary to underscore format.
- unescape_string(chars)
Unescapes the given chars.
- unescape_string(chars, map)
Unescapes the given chars according to the map given.
- unpipe(expr)
Breaks a pipeline expression into a list.
- update_meta(quoted, fun)
Applies the given function to the node metadata if it contains one.
- validate(expr)
Validates the given expressions are valid quoted expressions.
- var(var, context)
Generates an AST node representing the variable given by the atoms
var
andcontext
.
Types
captured_remote_function()
Specs
captured_remote_function() :: (... -> any())
A captured remote function in the format of &Mod.fun/arity
input()
Specs
input() :: input_expr() | {input(), input()} | [input()] | atom() | number() | binary()
The inputs of a macro
metadata()
Specs
metadata() :: keyword()
A keyword list of AST metadata.
The metadata in Elixir AST is a keyword list of values. Any key can be used and different parts of the compiler may use different keys. For example, the AST received by a macro will always include the :line
annotation, while the AST emitted by quote/2
will only have the :line
annotation if the :line
option is provided.
The following metadata keys are public:
-
:context
- Defines the context in which the AST was generated. For example,quote/2
will include the module callingquote/2
as the context. This is often used to distinguish regular code from code generated by a macro or byquote/2
. -
:counter
- The variable counter used for variable hygiene. In terms of the compiler, each variable is identified by the combination of eithername
andmetadata[:counter]
, orname
andcontext
. -
:generated
- Whether the code should be considered as generated by the compiler or not. This means the compiler and tools like Dialyzer may not emit certain warnings. -
:keep
- Used byquote/2
with the optionlocation: :keep
to annotate the file and the line number of the quoted source. -
:line
- The line number of the AST node.
The following metadata keys are enabled by Code.string_to_quoted/2
:
-
:closing
- contains metadata about the closing pair, such as a}
in a tuple or in a map, or such as the closing)
in a function call with parens. The:closing
does not delimit the end of expression if there are:do
and:end
metadata (when:token_metadata
is true) -
:column
- the column number of the AST node (when:columns
is true) -
:delimiter
- contains the opening delimiter for sigils, strings, and charlists as a string (such as"{"
,"/"
,"'"
, and the like) -
:format
- set to:keyword
when an atom is defined as a keyword -
:do
- contains metadata about thedo
location in a function call withdo/end
blocks (when:token_metadata
is true) -
:end
- contains metadata about theend
location in a function call withdo/end
blocks (when:token_metadata
is true) -
:end_of_expression
- denotes when the end of expression effectively happens. Available for all expressions except the last one inside a__block__
(when:token_metadata
is true)
The following metadata keys are private:
-
:alias
- Used for alias hygiene. -
:ambiguous_op
- Used for improved error messages in the compiler. -
:import
- Used for import hygiene. -
:var
- Used for improved error messages on undefined variables.
Do not rely on them as they may change or be fully removed in future versions of the language. They are often used by quote/2
and the compiler to provide features like hygiene, better error messages, and so forth.
If you introduce custom keys into the AST metadata, please make sure to prefix them with the name of your library or application, so that they will not conflict with keys that could potentially be introduced by the compiler in the future.
output()
Specs
output() :: output_expr() | {output(), output()} | [output()] | atom() | number() | binary() | captured_remote_function() | pid()
The output of a macro
t()
Specs
t() :: input()
Abstract Syntax Tree (AST)
Functions
camelize(string)
Specs
camelize(String.t()) :: String.t()
Converts the given string to CamelCase format.
This function was designed to camelize language identifiers/tokens, that's why it belongs to the Macro
module. Do not use it as a general mechanism for camelizing strings as it does not support Unicode or characters that are not valid in Elixir identifiers.
Examples
iex> Macro.camelize("foo_bar") "FooBar"
If uppercase characters are present, they are not modified in any way as a mechanism to preserve acronyms:
iex> Macro.camelize("API.V1") "API.V1" iex> Macro.camelize("API_SPEC") "API_SPEC"
decompose_call(ast)
Specs
decompose_call(t()) :: {atom(), [t()]} | {t(), atom(), [t()]} | :error
Decomposes a local or remote call into its remote part (when provided), function name and argument list.
Returns :error
when an invalid call syntax is provided.
Examples
iex> Macro.decompose_call(quote(do: foo)) {:foo, []} iex> Macro.decompose_call(quote(do: foo())) {:foo, []} iex> Macro.decompose_call(quote(do: foo(1, 2, 3))) {:foo, [1, 2, 3]} iex> Macro.decompose_call(quote(do: Elixir.M.foo(1, 2, 3))) {{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]} iex> Macro.decompose_call(quote(do: 42)) :error
escape(expr, opts \\ [])
Specs
escape(term(), keyword()) :: t()
Recursively escapes a value so it can be inserted into a syntax tree.
Examples
iex> Macro.escape(:foo) :foo iex> Macro.escape({:a, :b, :c}) {:{}, [], [:a, :b, :c]} iex> Macro.escape({:unquote, [], [1]}, unquote: true) 1
Options
:unquote
- when true, this function leavesunquote/1
andunquote_splicing/1
statements unescaped, effectively unquoting the contents on escape. This option is useful only when escaping ASTs which may have quoted fragments in them. Defaults to false.-
:prune_metadata
- when true, removes metadata from escaped AST nodes. Note this option changes the semantics of escaped code and it should only be used when escaping ASTs, never values. Defaults to false.As an example,
ExUnit
stores the AST of every assertion, so when an assertion fails we can show code snippets to users. Without this option, each time the test module is compiled, we get a different MD5 of the module byte code, because the AST contains metadata, such as counters, specific to the compilation environment. By pruning the metadata, we ensure that the module is deterministic and reduce the amount of dataExUnit
needs to keep around.
Comparison to Kernel.SpecialForms.quote/2
The escape/2
function is sometimes confused with Kernel.SpecialForms.quote/2
, because the above examples behave the same with both. The key difference is best illustrated when the value to escape is stored in a variable.
iex> Macro.escape({:a, :b, :c}) {:{}, [], [:a, :b, :c]} iex> quote do: {:a, :b, :c} {:{}, [], [:a, :b, :c]} iex> value = {:a, :b, :c} iex> Macro.escape(value) {:{}, [], [:a, :b, :c]} iex> quote do: value {:value, [], __MODULE__} iex> value = {:a, :b, :c} iex> quote do: unquote(value) {:a, :b, :c}
escape/2
is used to escape values (either directly passed or variable bound), while Kernel.SpecialForms.quote/2
produces syntax trees for expressions.
expand(ast, env)
Receives an AST node and expands it until it can no longer be expanded.
Note this function does not traverse the AST, only the root node is expanded.
This function uses expand_once/2
under the hood. Check it out for more information and examples.
expand_once(ast, env)
Receives an AST node and expands it once.
The following contents are expanded:
- Macros (local or remote)
- Aliases are expanded (if possible) and return atoms
- Compilation environment macros (
__CALLER__/0
,__DIR__/0
,__ENV__/0
and__MODULE__/0
) - Module attributes reader (
@foo
)
If the expression cannot be expanded, it returns the expression itself. This function does not traverse the AST, only the root node is expanded.
expand_once/2
performs the expansion just once. Check expand/2
to perform expansion until the node can no longer be expanded.
Examples
In the example below, we have a macro that generates a module with a function named name_length
that returns the length of the module name. The value of this function will be calculated at compilation time and not at runtime.
Consider the implementation below:
defmacro defmodule_with_length(name, do: block) do length = length(Atom.to_charlist(name)) quote do defmodule unquote(name) do def name_length, do: unquote(length) unquote(block) end end end
When invoked like this:
defmodule_with_length My.Module do def other_function, do: ... end
The compilation will fail because My.Module
when quoted is not an atom, but a syntax tree as follows:
{:__aliases__, [], [:My, :Module]}
That said, we need to expand the aliases node above to an atom, so we can retrieve its length. Expanding the node is not straightforward because we also need to expand the caller aliases. For example:
alias MyHelpers, as: My defmodule_with_length My.Module do def other_function, do: ... end
The final module name will be MyHelpers.Module
and not My.Module
. With Macro.expand/2
, such aliases are taken into consideration. Local and remote macros are also expanded. We could rewrite our macro above to use this function as:
defmacro defmodule_with_length(name, do: block) do expanded = Macro.expand(name, __CALLER__) length = length(Atom.to_charlist(expanded)) quote do defmodule unquote(name) do def name_length, do: unquote(length) unquote(block) end end end
generate_arguments(amount, context)
Specs
generate_arguments(0, context :: atom()) :: []
generate_arguments(pos_integer(), context) :: [{atom(), [], context}, ...] when context: atom()
Generates AST nodes for a given number of required argument variables using Macro.var/2
.
Examples
iex> Macro.generate_arguments(2, __MODULE__) [{:arg1, [], __MODULE__}, {:arg2, [], __MODULE__}]
operator?(name, arity)
Specs
operator?(name :: atom(), arity()) :: boolean()
Returns true
if the given name and arity is an operator.
pipe(expr, call_args, position)
Specs
pipe(t(), t(), integer()) :: t()
Pipes expr
into the call_args
at the given position
.
postwalk(ast, fun)
Specs
postwalk(t(), (t() -> t())) :: t()
Performs a depth-first, post-order traversal of quoted expressions.
postwalk(ast, acc, fun)
Specs
postwalk(t(), any(), (t(), any() -> {t(), any()})) :: {t(), any()}
Performs a depth-first, post-order traversal of quoted expressions using an accumulator.
prewalk(ast, fun)
Specs
prewalk(t(), (t() -> t())) :: t()
Performs a depth-first, pre-order traversal of quoted expressions.
prewalk(ast, acc, fun)
Specs
prewalk(t(), any(), (t(), any() -> {t(), any()})) :: {t(), any()}
Performs a depth-first, pre-order traversal of quoted expressions using an accumulator.
quoted_literal?(term)
Specs
quoted_literal?(t()) :: boolean()
Returns true
if the given quoted expression represents a quoted literal.
Atoms, numbers, and functions are always literals. Binaries, lists, tuples, maps, and structs are only literals if all of their terms are also literals.
Examples
iex> Macro.quoted_literal?(quote(do: "foo")) true iex> Macro.quoted_literal?(quote(do: {"foo", 1})) true iex> Macro.quoted_literal?(quote(do: {"foo", 1, :baz})) true iex> Macro.quoted_literal?(quote(do: %{foo: "bar"})) true iex> Macro.quoted_literal?(quote(do: %URI{path: "/"})) true iex> Macro.quoted_literal?(quote(do: URI.parse("/"))) false iex> Macro.quoted_literal?(quote(do: {foo, var})) false
special_form?(name, arity)
Specs
special_form?(name :: atom(), arity()) :: boolean()
Returns true
if the given name and arity is a special form.
struct!(module, env)
Specs
struct!(module, Macro.Env.t()) :: %module{} when module: module()
Expands the struct given by module
in the given env
.
This is useful when a struct needs to be expanded at compilation time and the struct being expanded may or may not have been compiled. This function is even capable of expanding structs defined under the module being compiled.
It will raise CompileError
if the struct is not available.
to_string(tree, fun \\ fn _ast, string -> string end)
Specs
to_string(t(), (t(), String.t() -> String.t())) :: String.t()
Converts the given expression AST to a string.
The given fun
is called for every node in the AST with two arguments: the AST of the node being printed and the string representation of that same node. The return value of this function is used as the final string representation for that AST node.
This function discards all formatting of the original code.
Examples
iex> Macro.to_string(quote(do: foo.bar(1, 2, 3))) "foo.bar(1, 2, 3)" iex> Macro.to_string(quote(do: 1 + 2), fn ...> 1, _string -> "one" ...> 2, _string -> "two" ...> _ast, string -> string ...> end) "one + two"
traverse(ast, acc, pre, post)
Specs
traverse(t(), any(), (t(), any() -> {t(), any()}), (t(), any() -> {t(), any()})) :: {t(), any()}
Performs a depth-first traversal of quoted expressions using an accumulator.
underscore(atom)
Specs
underscore(atom() | String.t()) :: String.t()
Converts the given atom or binary to underscore format.
If an atom is given, it is assumed to be an Elixir module, so it is converted to a binary and then processed.
This function was designed to underscore language identifiers/tokens, that's why it belongs to the Macro
module. Do not use it as a general mechanism for underscoring strings as it does not support Unicode or characters that are not valid in Elixir identifiers.
Examples
iex> Macro.underscore("FooBar") "foo_bar" iex> Macro.underscore("Foo.Bar") "foo/bar" iex> Macro.underscore(Foo.Bar) "foo/bar"
In general, underscore
can be thought of as the reverse of camelize
, however, in some cases formatting may be lost:
iex> Macro.underscore("SAPExample") "sap_example" iex> Macro.camelize("sap_example") "SapExample" iex> Macro.camelize("hello_10") "Hello10"
unescape_string(chars)
Specs
unescape_string(String.t()) :: String.t()
Unescapes the given chars.
This is the unescaping behaviour used by default in Elixir single- and double-quoted strings. Check unescape_string/2
for information on how to customize the escaping map.
In this setup, Elixir will escape the following: \0
, \a
, \b
, \d
, \e
, \f
, \n
, \r
, \s
, \t
and \v
. Bytes can be given as hexadecimals via \xNN
and Unicode code points as \uNNNN
escapes.
This function is commonly used on sigil implementations (like ~r
, ~s
and others) which receive a raw, unescaped string.
Examples
iex> Macro.unescape_string("example\\n") "example\n"
In the example above, we pass a string with \n
escaped and return a version with it unescaped.
unescape_string(chars, map)
Specs
unescape_string(String.t(), (non_neg_integer() -> non_neg_integer() | false)) :: String.t()
Unescapes the given chars according to the map given.
Check unescape_string/1
if you want to use the same map as Elixir single- and double-quoted strings.
Map
The map must be a function. The function receives an integer representing the code point of the character it wants to unescape. Here is the default mapping function implemented by Elixir:
def unescape_map(unicode), do: true def unescape_map(hex), do: true def unescape_map(?0), do: ?0 def unescape_map(?a), do: ?\a def unescape_map(?b), do: ?\b def unescape_map(?d), do: ?\d def unescape_map(?e), do: ?\e def unescape_map(?f), do: ?\f def unescape_map(?n), do: ?\n def unescape_map(?r), do: ?\r def unescape_map(?s), do: ?\s def unescape_map(?t), do: ?\t def unescape_map(?v), do: ?\v def unescape_map(e), do: e
If the unescape_map/1
function returns false
, the char is not escaped and the backslash is kept in the string.
Hexadecimals and Unicode code points will be escaped if the map function returns true
for ?x
. Unicode code points if the map function returns true
for ?u
.
Examples
Using the unescape_map/1
function defined above is easy:
Macro.unescape_string("example\\n", &unescape_map(&1))
unpipe(expr)
Specs
unpipe(t()) :: [t()]
Breaks a pipeline expression into a list.
The AST for a pipeline (a sequence of applications of |>
) is similar to the AST of a sequence of binary operators or function applications: the top-level expression is the right-most :|>
(which is the last one to be executed), and its left-hand and right-hand sides are its arguments:
quote do: 100 |> div(5) |> div(2) #=> {:|>, _, [arg1, arg2]}
In the example above, the |>
pipe is the right-most pipe; arg1
is the AST for 100 |> div(5)
, and arg2
is the AST for div(2)
.
It's often useful to have the AST for such a pipeline as a list of function applications. This function does exactly that:
Macro.unpipe(quote do: 100 |> div(5) |> div(2)) #=> [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]
We get a list that follows the pipeline directly: first the 100
, then the div(5)
(more precisely, its AST), then div(2)
. The 0
as the second element of the tuples is the position of the previous element in the pipeline inside the current function application: {{:div, [], [5]}, 0}
means that the previous element (100
) will be inserted as the 0th (first) argument to the div/2
function, so that the AST for that function will become {:div, [], [100, 5]}
(div(100, 5)
).
update_meta(quoted, fun)
Specs
update_meta(t(), (keyword() -> keyword())) :: t()
Applies the given function to the node metadata if it contains one.
This is often useful when used with Macro.prewalk/2
to remove information like lines and hygienic counters from the expression for either storage or comparison.
Examples
iex> quoted = quote line: 10, do: sample() {:sample, [line: 10], []} iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line)) {:sample, [], []}
validate(expr)
Specs
validate(term()) :: :ok | {:error, term()}
Validates the given expressions are valid quoted expressions.
Checks the Macro.t/0
for the specification of a valid quoted expression.
It returns :ok
if the expression is valid. Otherwise it returns a tuple in the form of {:error, remainder}
where remainder
is the invalid part of the quoted expression.
Examples
iex> Macro.validate({:two_element, :tuple}) :ok iex> Macro.validate({:three, :element, :tuple}) {:error, {:three, :element, :tuple}} iex> Macro.validate([1, 2, 3]) :ok iex> Macro.validate([1, 2, 3, {4}]) {:error, {4}}
var(var, context)
Specs
var(var, context) :: {var, [], context} when var: atom(), context: atom()
Generates an AST node representing the variable given by the atoms var
and context
.
Examples
In order to build a variable, a context is expected. Most of the times, in order to preserve hygiene, the context must be __MODULE__/0
:
iex> Macro.var(:foo, __MODULE__) {:foo, [], __MODULE__}
However, if there is a need to access the user variable, nil can be given:
iex> Macro.var(:foo, nil) {:foo, [], nil}
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.10.4/Macro.html