Metaprogramming Elixir: code example error (page 22)

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)

  defp build_my_if(expr, do: if_block) do
    build_my_if(expr, do: if_block, else: nil)

  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)

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__)

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).