The code example on page 22 is intended to show how to re-create Elixir’s
if macro. However, it uses
if on line 3.
The macro block just passes the args to
if. This could be a typo and meant to be
my_if. If that’s the case, a simple substitution would result in the error
... cannot invoke macro my_if/2 before its definition.
defmacro my_if(expr, do: if_block), do: if(expr, do: if_block, else: nil) ^^
That leads me to believe the example can be re-written to be more similar to Elixir’s own implementation^1, e.g.
defmodule ControlFlow do defmacro my_if(expr, clauses) do build_my_if(expr, clauses) end defp build_my_if(expr, do: if_block) do build_my_if(expr, do: if_block, else: nil) end defp build_my_if(expr, do: if_block, else: else_block) do quote do case unquote(expr) do result when result in [false, nil] -> unquote(else_block) _ -> unquote(if_block) end end end end
Same idea, but instead of 2 macros we have 1 macro that passes to a private function with 2 clauses.
Further, using the built-in
if leads to an unexpected expansion, using the example:
iex> quote do ControlFlow.my_if 1 == 1, do: :ok end |> Macro.expand_once(__ENV__) :ok
I believe this is because Elixir’s implementation further reduces tautologies^2, which the example doesn’t. From the example, we’d expect to see
:case in the resulting AST (this confusion is what lead me here originally).