Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Upside Down Dimension of Elixir - ElixirConf

The Upside Down Dimension of Elixir - ElixirConf

Open the gate and enter into the Upside Down Dimension of Elixir that is metaprogramming. If you have become frustrated with boilerplate code in your modules, wanted to program in a language closer to your domain, or create new programming constructs in Elixir, then this presentation is for you. The format of this talk is a gentle introduction presenting key concepts reinforced with code demos. At the end of the presentation, you will have the tools to write basic macros and a solid foundation to explore more advanced concepts on your own.

Avatar for Nicholas Henry

Nicholas Henry

September 03, 2020
Tweet

More Decks by Nicholas Henry

Other Decks in Programming

Transcript

  1. @nicholasjhenry ElixirConf 2020 The Upside Down 
 Dimension of Elixir

    An introduction to Metaprogramming The Dimension Of @nicholasjhenry An Introduction to Metaprogramming
  2. Are you tired of having to write the same boilerplate

    code in multiple modules? https://strangerthings.fandom.com/wiki/The_Mind_Flayer/Gallery? fi le=S2E3-Mind_Flayer_emerging.jpg
  3. Do you wish you could program in a language that

    more closely reflected the domain you are working in? https://strangerthings.fandom.com/wiki/The_Demogorgon/Gallery? fi le=Ep1-MonsterSilhouette.png
  4. Is there a feature you wish existed in the Elixir

    language? https://strangerthings.fandom.com/wiki/Trick_or_Treat,_Freak/Gallery? fi le=Will_experiencing_another_episode_at_the_Upside_Down.png
  5. Open the gate into the Upside Down Dimension that is

    Metaprogramming in Elixir https://strangerthings.fandom.com/wiki/The_Gate_(episode)/Gallery? fi le=Stranger-things-2-ending-eleven-closes-portal-gateway.jpg
  6. Metaprogramming is a tool that gives you super powers like

    a Mind Flayer https://strangerthings.fandom.com/wiki/The_Mind_Flayer/Gallery? fi le=Mind_Flayer_in_Will%27s_episode.jpg
  7. Metaprogramming allows you to write code that generates code Mind

    Flayer Gallery: https://strangerthings.fandom.com/wiki/The_Mind_Flayer/Gallery? fi le=Mind_Flayer_gate_%282%29.jpg
  8. # iex> mix phx.new UpsideDown defmodule UpsideDownWeb do # Extends

    controllers, views, channels, router # ... def controller do # ... end # ... defmacro __using__(which) when is_atom(which) do apply(__MODULE__, which, []) end end defmodule UpsideDownWeb.PageController do use UpsideDownWeb, :controller def index(conn, params) do # ... end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
  9. 1 2 3 4 5 6 7 8 9 10

    11 12 13 markup do div id: "main" do h1 class: "title" do text "Welcome!" end end div class: "row" do div do p do: text "Hello!" end end end end
  10. Goals 1. Demystify metaprogramming in Elixir 2. Build a mental

    model and understand the core concepts 3. Read and write basic macros
  11. 1. Demo the requirements for a macro 2. Learn key

    concepts 3. Demo those concepts (emulated) 4. Multiple points of review to reinforce concepts 5. Implement the macro Approach
  12. Presentation Demo • Calculator.add/2 # adds two numbers • Tracer.trace/1

    # prints code and return value The “hello world” of metaprogramming
  13. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) # 👀 Running x + y returns 3 end end 1 2 3 4 5 6 7 editor: calculator.ex and tracer.ex defmodule Tracer do defmacro trace(ast) do # 👀 TODO: write the macro end end 1 2 3 4 5 6
  14. Introduction: Example Macro iex > Calculator.add(1, 2) a = 3

    iex > 3 Running x + y returns 3 3 iex >
  15. Introduction: Example Macro iex > Calculator.add(1, 2) a = 3

    b = 5 iex > 3 Running x + y returns 3 3 iex >
  16. Introduction: Example Macro iex > Calculator.add(1, 2) a = 3

    b = 5 iex > 3 iex > 5 Running x + y returns 3 3 iex >
  17. Introduction: Example Macro iex > Calculator.add(1, 2) require Tracer a

    = 3 b = 5 iex > 3 iex > 5 Running x + y returns 3 3 iex >
  18. Introduction: Example Macro iex > Calculator.add(1, 2) require Tracer a

    = 3 b = 5 iex > 3 iex > 5 iex > Tracer Running x + y returns 3 3 iex >
  19. Introduction: Example Macro iex > Calculator.add(1, 2) require Tracer Tracer.trace(a

    * b) a = 3 b = 5 iex > 3 iex > 5 iex > Tracer Running x + y returns 3 3 iex >
  20. Introduction: Example Macro iex > Calculator.add(1, 2) require Tracer Tracer.trace(a

    * b) Running a * b returns 15 15 a = 3 b = 5 iex > 3 iex > 5 iex > Tracer Running x + y returns 3 3 iex >
  21. Two Realities Human World Upside Down Dimension (business logic) (code

    generation) Part 1 run-time compile-time Part 2 high level syntax internal data structure Part 3 functions macros
  22. Two instances Runtime != Compile-time Run-time Compile-time Instance running an

    executable convert code to an executable Errors exceptions compile-time Cause state syntax
  23. Compilation Process EX Elixir Source Code Compiler Byte Code Erlang

    VM Compile-time Run-time elixirc calculator.ex Calculator.beam elixir --eval \ "Calculator.add(1, 2)" Macros invoked generating code Calls to macros vim calculator.ex
  24. Demo Part 1: Runtime != Compile time 1. Code can

    be executed at compile-time 2. Compile-time is static, run-time is dynamic
  25. defmodule Calculator do @time Time.utc_now IO.puts(“Compiled at #{@time}”) def add(x,

    y) do IO.puts(“Compiled at #{@time}”) IO.puts(“Runtime #{Time.utc_now}) x + y end end 1 2 3 4 5 6 7 8 9 10 11 12 iex > Part 1, Demo: Runtime != Compile time
  26. defmodule Calculator do @time Time.utc_now IO.puts(“Compiled at #{@time}”) def add(x,

    y) do IO.puts(“Compiled at #{@time}”) IO.puts(“Runtime #{Time.utc_now}) x + y end end 1 2 3 4 5 6 7 8 9 10 11 12 iex > c "./calculator.ex" Part 1, Demo: Runtime != Compile time
  27. defmodule Calculator do @time Time.utc_now IO.puts(“Compiled at #{@time}”) def add(x,

    y) do IO.puts(“Compiled at #{@time}”) IO.puts(“Runtime #{Time.utc_now}) x + y end end 1 2 3 4 5 6 7 8 9 10 11 12 iex > c "./calculator.ex" Part 1, Demo: Runtime != Compile time Compiled at 14:30:00.000000 iex > Calculator
  28. defmodule Calculator do @time Time.utc_now IO.puts(“Compiled at #{@time}”) def add(x,

    y) do IO.puts(“Compiled at #{@time}”) IO.puts(“Runtime #{Time.utc_now}) x + y end end 1 2 3 4 5 6 7 8 9 10 11 12 iex > c "./calculator.ex" Calculator.add(1, 2) Part 1, Demo: Runtime != Compile time Compiled at 14:30:00.000000 iex > Calculator
  29. defmodule Calculator do @time Time.utc_now IO.puts(“Compiled at #{@time}”) def add(x,

    y) do IO.puts(“Compiled at #{@time}”) IO.puts(“Runtime #{Time.utc_now}) x + y end end 1 2 3 4 5 6 7 8 9 10 11 12 iex > c "./calculator.ex" Calculator.add(1, 2) Part 1, Demo: Runtime != Compile time Compiled at 14:30:00.000000 iex > Calculator Compiled at 14:30:00.000000 Runtime 14:30:02.000000 3
  30. Review Part 1: Runtime != Compile time Run-time Compile-time execute

    code execute code dynamic static execute byte-code generate byte-code
  31. Compilation Process Elixir Source Code Byte Code Compiler Parser Generator

    {:+, [ ], 1, 2} Internal Data Structure (representation of an Elixir expression) EX
  32. Part 2: High-level Syntax != Internal Data Structure 1. `quote/2`

    2. Explore the representation of an Elixir expression Demo
  33. iex > quote do 1 + 2 end Part 2,

    Demo 1: Using quote/2 to produce an AST
  34. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > Part 2, Demo 1: Using quote/2 to produce an AST
  35. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator Part 2, Demo 1: Using quote/2 to produce an AST
  36. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > Part 2, Demo 1: Using quote/2 to produce an AST
  37. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST
  38. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > Part 2, Demo 1: Using quote/2 to produce an AST
  39. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: Part 2, Demo 1: Using quote/2 to produce an AST
  40. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > Part 2, Demo 1: Using quote/2 to produce an AST
  41. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > _ = Kernel.+(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST
  42. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > _ = Kernel.+(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST 3 iex >
  43. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > _ = Kernel.+(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST 3 iex > quote do 1 + 2 end
  44. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > _ = Kernel.+(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST 3 iex > quote do 1 + 2 end {:+, [context: Elixir, import: Kernel], [1, 2]} iex >
  45. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > _ = Kernel.+(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST 3 iex > quote do 1 + 2 end {:+, [context: Elixir, import: Kernel], [1, 2]} iex > # {:function, metadata, [arg1, arg2, argN, …]}
  46. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > _ = Kernel.+(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST 3 iex > quote do 1 + 2 end {:+, [context: Elixir, import: Kernel], [1, 2]} iex > # {:function, metadata, [arg1, arg2, argN, …]} iex >
  47. iex > quote do 1 + 2 end {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex > # 1 + 2: The above syntax is know as an infix operator iex > +(1, 2) ** (SyntaxError) iex:6: syntax error before: ‘)' iex > # The equivalent function call is: iex > _ = Kernel.+(1, 2) Part 2, Demo 1: Using quote/2 to produce an AST 3 iex > quote do 1 + 2 end {:+, [context: Elixir, import: Kernel], [1, 2]} iex > # {:function, metadata, [arg1, arg2, argN, …]} # Abstract Syntax Tree (AST) / Macro.t iex >
  48. quote do (1 + (2 * 3)) - 4 end

    iex > Part 2, Demo 2: Exploring the AST
  49. quote do (1 + (2 * 3)) - 4 end

    iex > {:-, _, [ {:+, _, [ 1, {:*,_, [2, 3]} ]}, 4 ] } iex > Part 2, Demo 2: Exploring the AST
  50. # 👀 Step 1: 2 * 3 = 6 quote

    do (1 + (2 * 3)) - 4 end iex > {:-, _, [ {:+, _, [ 1, {:*,_, [2, 3]} ]}, 4 ] } iex > Part 2, Demo 2: Exploring the AST
  51. # 👀 Step 1: 2 * 3 = 6 quote

    do (1 + (2 * 3)) - 4 end iex > {:-, _, [ {:+, _, [ 1, {:*,_, [2, 3]} ]}, 4 ] } iex > Part 2, Demo 2: Exploring the AST # 👀 Step 2: 1 + 6 = 7
  52. # 👀 Step 1: 2 * 3 = 6 quote

    do (1 + (2 * 3)) - 4 end iex > {:-, _, [ {:+, _, [ 1, {:*,_, [2, 3]} ]}, 4 ] } iex > Part 2, Demo 2: Exploring the AST # 👀 Step 2: 1 + 6 = 7 # 👀 Step 3: 7 - 4 = 3
  53. # 👀 Step 1: 2 * 3 = 6 quote

    do (1 + (2 * 3)) - 4 end iex > {:-, _, [ {:+, _, [ 1, {:*,_, [2, 3]} ]}, 4 ] } iex > Part 2, Demo 2: Exploring the AST # 👀 Step 2: 1 + 6 = 7 # 👀 Step 3: 7 - 4 = 3 # Abstract: essential elements only/machine optimized representation iex >
  54. # 👀 Step 1: 2 * 3 = 6 quote

    do (1 + (2 * 3)) - 4 end iex > {:-, _, [ {:+, _, [ 1, {:*,_, [2, 3]} ]}, 4 ] } iex > Part 2, Demo 2: Exploring the AST # 👀 Step 2: 1 + 6 = 7 # 👀 Step 3: 7 - 4 = 3 # Abstract: essential elements only/machine optimized representation iex > # Syntax: structure of statements iex >
  55. # 👀 Step 1: 2 * 3 = 6 quote

    do (1 + (2 * 3)) - 4 end iex > {:-, _, [ {:+, _, [ 1, {:*,_, [2, 3]} ]}, 4 ] } iex > Part 2, Demo 2: Exploring the AST # 👀 Step 2: 1 + 6 = 7 # 👀 Step 3: 7 - 4 = 3 # Abstract: essential elements only/machine optimized representation iex > # Syntax: structure of statements iex > # Tree: nested set of nodes (tuples)
  56. Review Part 2: High-level Syntax != Internal Data Structures •

    `quote/2` generates an internal data structure • Abstract Syntax Tree (AST) • `{function, metadata, args}` => `Macro.t`
  57. Comparison Part 3: Functions != Macros def/2 defmacro/2 `term` =>

    `term` `Macro.t` => `Macro.t` invoked at run-time invoked at compile-time current context macro and caller context
  58. Compilation Process Elixir Source Code Byte Code Parser Generator {:+,

    [ ], 1, 2} Abstract Syntax Tree (AST) EX Compilation Process Compiler
  59. Compilation Process AST with Macros {:inspect, _, _} AST with

    generated Code {:trace, _, _} Parser Generator Expansion Compiler Elixir Source Code Byte Code EX
  60. Demo Part 3: Functions != Macros 1. Macro AST 2.

    Macros invoked at compile time 3. Macro and Caller context 4. Macro Expansion
  61. defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end

    Part 3 Demo 1: Demonstrate AST 1 2 3 4 5 iex > Tracer.trace(1 + 2)
  62. defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end

    Part 3 Demo 1: Demonstrate AST 1 2 3 4 5 iex > Tracer.trace(1 + 2) ** (CompileError) iex:7: you must require Tracer before invoking the macro Tracer.trace/1 iex >
  63. defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end

    Part 3 Demo 1: Demonstrate AST 1 2 3 4 5 iex > Tracer.trace(1 + 2) ** (CompileError) iex:7: you must require Tracer before invoking the macro Tracer.trace/1 iex > require Tracer
  64. defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end

    Part 3 Demo 1: Demonstrate AST 1 2 3 4 5 iex > Tracer.trace(1 + 2) ** (CompileError) iex:7: you must require Tracer before invoking the macro Tracer.trace/1 iex > require Tracer Tracer iex >
  65. defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end

    Part 3 Demo 1: Demonstrate AST 1 2 3 4 5 iex > Tracer.trace(1 + 2) ** (CompileError) iex:7: you must require Tracer before invoking the macro Tracer.trace/1 iex > require Tracer Tracer iex > Tracer.trace(1 + 2)
  66. compile-time: {:+, _, [1, 2]} 3 defmodule Tracer do defmacro

    trace(ast) do IO.inspect(ast, "compile-time") end end Part 3 Demo 1: Demonstrate AST 1 2 3 4 5 iex > Tracer.trace(1 + 2) ** (CompileError) iex:7: you must require Tracer before invoking the macro Tracer.trace/1 iex > require Tracer Tracer iex > Tracer.trace(1 + 2)
  67. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end Part 3 Demo 2: defmacro invoked at compile iex > 1 2 3 4 5 1 2 3 4 5 6 7
  68. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end Part 3 Demo 2: defmacro invoked at compile iex > c “./calculator.ex" 1 2 3 4 5 1 2 3 4 5 6 7
  69. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end Part 3 Demo 2: defmacro invoked at compile iex > c “./calculator.ex" compile-time: {:+, _, [{:x, _, nil}, {:y, _, nil}] } iex > 1 2 3 4 5 1 2 3 4 5 6 7
  70. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end Part 3 Demo 2: defmacro invoked at compile iex > c “./calculator.ex" compile-time: {:+, _, [{:x, _, nil}, {:y, _, nil}] } iex > Calculator.add(1, 2) 1 2 3 4 5 1 2 3 4 5 6 7
  71. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(ast, "compile-time") end end Part 3 Demo 2: defmacro invoked at compile iex > c “./calculator.ex" compile-time: {:+, _, [{:x, _, nil}, {:y, _, nil}] } iex > Calculator.add(1, 2) 3 1 2 3 4 5 1 2 3 4 5 6 7
  72. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, “Caller context at runtime") end end end Part 3 Demo 3: Macro and Caller Context 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 iex >
  73. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, “Caller context at runtime") end end end Part 3 Demo 3: Macro and Caller Context 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 iex > r Calculator
  74. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, “Caller context at runtime") end end end Part 3 Demo 3: Macro and Caller Context 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 iex > r Calculator Macro context at compile time: Tracer iex >
  75. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, “Caller context at runtime") end end end Part 3 Demo 3: Macro and Caller Context 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 iex > r Calculator Macro context at compile time: Tracer iex > Calculator.add(1, 2)
  76. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, “Caller context at runtime") end end end Part 3 Demo 3: Macro and Caller Context Caller context at runtime: Calculator 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 iex > r Calculator Macro context at compile time: Tracer iex > Calculator.add(1, 2)
  77. Part 3 Demo 4: Macro Expansion iex > defmodule Tracer

    do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  78. Part 3 Demo 4: Macro Expansion iex > require Tracer

    defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  79. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  80. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  81. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  82. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  83. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  84. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > Tracer.trace(1 + 2) defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  85. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > ... > Tracer.trace(1 + 2) defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  86. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > ... > Tracer.trace(1 + 2) end defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  87. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > ... > Tracer.trace(1 + 2) end {{:., [], [{:__aliases__, _, [:Tracer]}, :trace]}, [], [{:+, _, [1, 2]}] } iex > defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  88. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > ... > Tracer.trace(1 + 2) end {{:., [], [{:__aliases__, _, [:Tracer]}, :trace]}, [], [{:+, _, [1, 2]}] } iex > # expansion phase defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  89. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > ... > Tracer.trace(1 + 2) end {{:., [], [{:__aliases__, _, [:Tracer]}, :trace]}, [], [{:+, _, [1, 2]}] } iex > # expansion phase iex > defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  90. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > ... > Tracer.trace(1 + 2) end Macro.expand(quoted, __ENV__) {{:., [], [{:__aliases__, _, [:Tracer]}, :trace]}, [], [{:+, _, [1, 2]}] } iex > # expansion phase iex > defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11
  91. Part 3 Demo 4: Macro Expansion iex > require Tracer

    iex > # parsing phase iex > quoted = quote do ... > ... > Tracer.trace(1 + 2) end Macro.expand(quoted, __ENV__) {{:., [], [{:__aliases__, _, [:Tracer]}, :trace]}, [], [{:+, _, [1, 2]}] } iex > # expansion phase iex > defmodule Tracer do defmacro trace(ast) do IO.inspect(__MODULE__, “Macro context at compile time”) quote do IO.inspect(__MODULE__, "Caller context at runtime") end end end 1 2 3 4 5 6 7 8 9 10 11 {{:., [], [ {:__aliases__, _, [:IO]}, :inspect ]}, [], [ {:__MODULE__, _, Tracer}, "Caller context at runtime" ]} Macro context at compile time: Tracer
  92. Review Part 3: Functions != Macros def/2 defmacro/2 `term` =>

    `term` `Macro.t` => `Macro.t` invoked at run-time invoked at compile time current context macro and caller context
  93. Demo: unquote/2 iex > quote do x + 2 end

    iex > {:+, _, [{:x, _, _}, 2]}
  94. Demo: unquote/2 iex > quote do x + 2 end

    x = 1 iex > {:+, _, [{:x, _, _}, 2]}
  95. Demo: unquote/2 iex > quote do x + 2 end

    x = 1 iex > {:+, _, [{:x, _, _}, 2]} iex > 1
  96. Demo: unquote/2 iex > quote do x + 2 end

    x = 1 iex > {:+, _, [{:x, _, _}, 2]} quote do x + 2 end iex > 1
  97. Demo: unquote/2 iex > quote do x + 2 end

    x = 1 iex > {:+, _, [{:x, _, _}, 2]} quote do x + 2 end iex > {:+, _, [{:x, _, _}, 2]} iex > 1
  98. Demo: unquote/2 iex > quote do x + 2 end

    x = 1 quote do unquote(x) + 2 end # 👀 “#{x} + 2” iex > {:+, _, [{:x, _, _}, 2]} quote do x + 2 end iex > {:+, _, [{:x, _, _}, 2]} iex > 1
  99. Demo: unquote/2 iex > quote do x + 2 end

    x = 1 quote do unquote(x) + 2 end # 👀 “#{x} + 2” iex > {:+, _, [{:x, _, _}, 2]} quote do x + 2 end iex > {:+, _, [{:x, _, _}, 2]} {:+, _, [1, 2]} iex > 1
  100. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 editor: calculator.ex
  101. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 editor: calculator.ex # 👀 compile time dependency
  102. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 editor: calculator.ex # 👀 compile time dependency # 👀 macro, invoked at compile-time, AST
  103. defmodule Tracer do defmacro trace(ast) do end end 1 2

    3 4 5 6 7 8 9 10 11 12 editor: tracer.ex # 👀 Macro.t => Macro.t
  104. defmodule Tracer do defmacro trace(ast) do end end 1 2

    3 4 5 6 7 8 9 10 11 12 editor: tracer.ex # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  105. defmodule Tracer do defmacro trace(ast) do end end 1 2

    3 4 5 6 7 8 9 10 11 12 editor: tracer.ex result = unquote(ast) result # 👀 {:+, _, [x, y]} # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  106. defmodule Tracer do defmacro trace(ast) do end end 1 2

    3 4 5 6 7 8 9 10 11 12 editor: tracer.ex result = unquote(ast) result # 👀 {:+, _, [x, y]} :ok = IO.puts(msg) # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  107. defmodule Tracer do defmacro trace(ast) do end end msg =

    “Running x + y returns 3” 1 2 3 4 5 6 7 8 9 10 11 12 editor: tracer.ex result = unquote(ast) result # 👀 {:+, _, [x, y]} :ok = IO.puts(msg) # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  108. defmodule Tracer do defmacro trace(ast) do end end 1 2

    3 4 5 6 7 8 9 10 11 12 editor: tracer.ex result = unquote(ast) result # 👀 {:+, _, [x, y]} :ok = IO.puts(msg) # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  109. defmodule Tracer do defmacro trace(ast) do end end msg =

    “Running x + y returns #{result}” 1 2 3 4 5 6 7 8 9 10 11 12 editor: tracer.ex result = unquote(ast) result # 👀 {:+, _, [x, y]} :ok = IO.puts(msg) # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  110. defmodule Tracer do defmacro trace(ast) do end end msg =

    “Running x + y returns #{result}” 1 2 3 4 5 6 7 8 9 10 11 12 editor: tracer.ex # 👀 {:+, [], [x, y]} => “x + y” src = Macro.to_string(ast) result = unquote(ast) result # 👀 {:+, _, [x, y]} :ok = IO.puts(msg) # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  111. defmodule Tracer do defmacro trace(ast) do end end 1 2

    3 4 5 6 7 8 9 10 11 12 editor: tracer.ex # 👀 {:+, [], [x, y]} => “x + y” src = Macro.to_string(ast) result = unquote(ast) result # 👀 {:+, _, [x, y]} :ok = IO.puts(msg) # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  112. defmodule Tracer do defmacro trace(ast) do end end msg =

    “Running #{unquote(src)} returns #{result}” 1 2 3 4 5 6 7 8 9 10 11 12 editor: tracer.ex # 👀 {:+, [], [x, y]} => “x + y” src = Macro.to_string(ast) result = unquote(ast) result # 👀 {:+, _, [x, y]} :ok = IO.puts(msg) # 👀 Macro.t => Macro.t quote do end # 👀 generate Macro.t
  113. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 defmodule Tracer do defmacro trace(ast) do src = Macro.to_string(ast) quote do result = unquote(ast) msg = “Running #{unquote(src)} returns #{result}” :ok = IO.puts(msg) result end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Running the macro iex >
  114. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 defmodule Tracer do defmacro trace(ast) do src = Macro.to_string(ast) quote do result = unquote(ast) msg = “Running #{unquote(src)} returns #{result}” :ok = IO.puts(msg) result end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Running the macro iex > Calculator.add(1, 2)
  115. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 defmodule Tracer do defmacro trace(ast) do src = Macro.to_string(ast) quote do result = unquote(ast) msg = “Running #{unquote(src)} returns #{result}” :ok = IO.puts(msg) result end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Running the macro iex > Calculator.add(1, 2) Running x + y returns 3 3 iex >
  116. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 defmodule Tracer do defmacro trace(ast) do src = Macro.to_string(ast) quote do result = unquote(ast) msg = “Running #{unquote(src)} returns #{result}” :ok = IO.puts(msg) result end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Running the macro iex > Calculator.add(1, 2) Running x + y returns 3 3 iex > Calculator.add(3, 10)
  117. defmodule Calculator do require Tracer def add(x, y) do Tracer.trace(x

    + y) end end 1 2 3 4 5 6 7 defmodule Tracer do defmacro trace(ast) do src = Macro.to_string(ast) quote do result = unquote(ast) msg = “Running #{unquote(src)} returns #{result}” :ok = IO.puts(msg) result end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Running the macro iex > Calculator.add(1, 2) Running x + y returns 3 3 iex > Calculator.add(3, 10) Running x + y returns 13 13
  118. Two Realities Human World Upside Down Dimension (business logic) (code

    generation) Part 1 run-time compile-time Part 2 high level syntax internal data structure Part 3 functions macros
  119. Next Steps 1. Review the Phoenix generated “Web” module 2.

    Experiment with the examples:
 https://github.com/nicholasjhenry/upside-down-elixir 3. “Metaprogramming Elixir” by Chris McCord