Powered by AppSignal & Oban Pro

chapter_1

chapter_1.livemd

chapter_1

Math

defmodule Math do
  defmacro say({:+, _, [lhs, rhs]}) do
    quote do
      lhs = unquote(lhs)
      rhs = unquote(rhs)

      result = lhs + rhs
      IO.puts("#{lhs} plus #{rhs} is #{result}")
      result
    end
  end

  defmacro say({:*, _, [lhs, rhs]}) do
    quote do
      lhs = unquote(lhs)
      rhs = unquote(rhs)

      result = lhs * rhs
      IO.puts("#{lhs} times #{rhs} is #{result}")
      result
    end
  end
  
end
require Math

Math.say(5 + 2)
require Math

Math.say(5 * 2)

The Building Blocks of Elixir

Re-Creating Elixir’s unless Macro

defmodule ControlFlow do
  defmacro unless(expression, do: block) do
    quote do
      if !unquote(expression), do: unquote(block)
    end
  end
end
require ControlFlow

ControlFlow.unless 2 == 5, do: "block entered"
ControlFlow.unless 2 == 2 do
  "block entered"
end

Expanding the function

ast = quote do
  ControlFlow.unless 2 = 5, do: "block entered"
end
expanded_once = Macro.expand_once(ast, __ENV__)
expanded_fully = Macro.expand_once(expanded_once, __ENV__)

unquote

Without unquote

number = 5

ast = quote do
  number + 10
end
Code.eval_quoted(ast)

With unquote

number = 5

ast = quote do
  unquote(number) + 10
end
Code.eval_quoted(ast)

Code Injection and the Caller’s Context

defmodule Mod do
  defmacro definfo do
    IO.puts "In macro`s context (#{__MODULE__})."

    quote do
      IO.puts "In caller`s context (#{__MODULE__})"
      
      def friendly_info do
        IO.puts """
        My name is #{__MODULE__}
        My functions are #{inspect __MODULE__.__info__(:functions)}
        """
      end
    end
  end
end
defmodule MyModule do
  require Mod
  
  Mod.definfo
end
MyModule.friendly_info()

Overriding Hygiene

ast = quote do
  if var!(meaning_of_life) == 42 do
    "It is time"
  else
    "It remains to be seen"
  end
end
Code.eval_quoted(ast, meaning_of_life: 42)

If we have scope hygiene in place varible dont like outside

defmodule Setter1 do
  defmacro bind_name(string) do
    quote do
      name = unquote(string)
    end
  end
end
require Setter1
name = "Chris"
Setter1.bind_name("max")
name

Code without hygiene protection

defmodule Setter2 do
  defmacro bind_name(string) do
    quote do
      var!(name) = unquote(string)
    end
  end
end
require Setter2
name = "King"
Setter2.bind_name("Queen")
name