Macros: The Building Blocks
Learn to build custom language features using macros.
We'll cover the following...
Recreating Elixir’s unless macro
Suppose that Elixir lacks a built-in unless construct. In most languages, we would have to settle for if! expressions and learn to accept this syntactic shortcoming.
Fortunately for us, Elixir isn’t like most languages.
Let’s define our own unless macro, using if as a building block of our implementation. Macros must be defined within modules, so let’s define a ControlFlow module.
defmodule ControlFlow do
defmacro unless(expression, do: block) do
quote do
if !unquote(expression), do: unquote(block)
end
end
endNote: Run the project and try the commands given below:
iex> c "unless.exs"
# Output: [ControlFlow]
iex> require ControlFlow
# Output: nil
iex> ControlFlow.unless 2 == 5, do: "block entered"
# Output: "block entered"
iex> ControlFlow.unless 5 == 5 do
...> "block entered"
...> end
# Output: nil
We must first require ControlFlow before invoking its macros in cases where the module hasn’t already been imported.
Since macros receive the AST representation of arguments, we can accept any valid Elixir expression as the first argument to unless on line 2. In our second argument, we can pattern-match the provided do/end block and bind its AST value to a variable.
Remember, a macro’s purpose is to ...