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

Don't Write Macros - But Do Learn How They Work

Don't Write Macros - But Do Learn How They Work

September 7, 2017 @ ElixirConf

Jesse J. Anderson

September 07, 2017
Tweet

More Decks by Jesse J. Anderson

Other Decks in Technology

Transcript

  1. Code is read many more times than it is written,

    which means that the ultimate cost of code is in its reading. ~ Sandi Metz ~
  2. if

  3. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that")
  4. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that")
  5. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that")
  6. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that")
  7. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that")
  8. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that")
  9. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that") if(1 + 2 == 3, [do: "this", else: "that"])
  10. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that") if(1 + 2 == 3, [do: "this", else: "that"])
  11. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that") if(1 + 2 == 3, [do: "this", else: "that"])
  12. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that") if(1 + 2 == 3, [do: "this", else: "that"])
  13. if 1 + 2 == 3, do: "this", else: "that"

    if(1 + 2 == 3, do: "this", else: "that") if(1 + 2 == 3, [do: "this", else: "that"])
  14. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3)
  15. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3)
  16. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3)
  17. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3)
  18. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3)
  19. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3)
  20. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3) Kernel.==(+(1, 2), 3)
  21. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3) Kernel.==(+(1, 2), 3)
  22. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3) Kernel.==(+(1, 2), 3)
  23. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3) Kernel.==(+(1, 2), 3) Kernel.==(Kernel.+(1, 2), 3)
  24. 1 + 2 == 3 (1 + 2) == 3

    +(1, 2) == 3 ==(+(1, 2), 3) Kernel.==(+(1, 2), 3) Kernel.==(Kernel.+(1, 2), 3)
  25. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Credit Michał Muskała
  26. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST Credit Michał Muskała
  27. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Credit Michał Muskała
  28. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST Credit Michał Muskała
  29. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Credit Michał Muskała
  30. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Compile Pattern Matching Credit Michał Muskała
  31. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Compile Pattern Matching (now we have "kernel Erlang") Credit Michał Muskała
  32. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Compile Pattern Matching (now we have "kernel Erlang") Run Kernel Optimizations Credit Michał Muskała
  33. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Compile Pattern Matching (now we have "kernel Erlang") Run Kernel Optimizations Generate Assembly Credit Michał Muskała
  34. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Compile Pattern Matching (now we have "kernel Erlang") Run Kernel Optimizations Generate Assembly Optimize Assembly Credit Michał Muskała
  35. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Compile Pattern Matching (now we have "kernel Erlang") Run Kernel Optimizations Generate Assembly Optimize Assembly Generate Bytecode Credit Michał Muskała
  36. Steps of Elixir Compiler Read file Tokenize Parse (builds AST)

    Expand Macros Apply Rewrite Rules Lower Elixir AST to Erlang AST (now Erlang compiler takes over) Compile Erlang AST to Core AST
 Core Optimizations & True Inlining Compile Pattern Matching (now we have "kernel Erlang") Run Kernel Optimizations Generate Assembly Optimize Assembly Generate Bytecode Write .beam file Credit Michał Muskała
  37. Step 1: Take code Step 2: Build Abstract Syntax Tree

    Step 3: ????? Step 4: Profit! The Beam!
  38. atoms integers floats strings lists 2-element
 tuples
 :foo 42 13.1

    "hello" [1, 2, 3] {"hello", :world} Quote Literals:
  39. atoms integers floats strings lists 2-element
 tuples
 :foo 42 13.1

    "hello" [1, 2, 3] {"hello", :world} Quote Literals: 3-element Tuples:
  40. atoms integers floats strings lists 2-element
 tuples
 :foo 42 13.1

    "hello" [1, 2, 3] {"hello", :world} variable {name, meta, context} Quote Literals: 3-element Tuples:
  41. atoms integers floats strings lists 2-element
 tuples
 :foo 42 13.1

    "hello" [1, 2, 3] {"hello", :world} variable {name, meta, context} {:foo, [], Elixir} Quote Literals: 3-element Tuples:
  42. atoms integers floats strings lists 2-element
 tuples
 :foo 42 13.1

    "hello" [1, 2, 3] {"hello", :world} variable {name, meta, context} {:foo, [], Elixir} call {function, context/meta, args} Quote Literals: 3-element Tuples:
  43. atoms integers floats strings lists 2-element
 tuples
 :foo 42 13.1

    "hello" [1, 2, 3] {"hello", :world} variable {name, meta, context} {:foo, [], Elixir} call {function, context/meta, args} Quote Literals: 3-element Tuples:
  44. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> quote do: :atom
  45. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> quote do: :atom :atom iex(3)>
  46. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> quote do: :atom :atom iex(3)> quote do: [1, 2, 3]
  47. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> quote do: :atom :atom iex(3)> quote do: [1, 2, 3] [1, 2, 3] iex(4)>
  48. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> foo = 2 2 iex(3)>
  49. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> foo = 2 2 iex(3)> quote do: 1 + foo
  50. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> foo = 2 2 iex(3)> quote do: 1 + foo {:+, [context: Elixir, import: Kernel], [1, {:foo, [], Elixir}]} iex(4)>
  51. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> foo = 2 2 iex(3)> quote do: 1 + foo {:+, [context: Elixir, import: Kernel], [1, {:foo, [], Elixir}]} iex(4)>
  52. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> foo = 2 2 iex(3)> quote do: 1 + foo {:+, [context: Elixir, import: Kernel], [1, {:foo, [], Elixir}]} iex(4)>
  53. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> foo = 2 2 iex(3)> quote do: 1 + foo {:+, [context: Elixir, import: Kernel], [1, {:foo, [], Elixir}]} iex(4)> quote do: 1 + unquote(foo)
  54. iex(1)> quote do: 1 + 2 {:+, [context: Elixir, import:

    Kernel], [1, 2]} iex(2)> foo = 2 2 iex(3)> quote do: 1 + foo {:+, [context: Elixir, import: Kernel], [1, {:foo, [], Elixir}]} iex(4)> quote do: 1 + unquote(foo) {:+, [context: Elixir, import: Kernel], [1, 2]} iex(5)>
  55. iex(1)> foo = 2 2 iex(2)> "the value is foo"

    "the value is foo" iex(3)> "the value is #{foo}"
  56. iex(1)> foo = 2 2 iex(2)> "the value is foo"

    "the value is foo" iex(3)> "the value is #{foo}"
  57. iex(1)> foo = 2 2 iex(2)> "the value is foo"

    "the value is foo" iex(3)> "the value is #{foo}"
  58. iex(1)> foo = 2 2 iex(2)> "the value is foo"

    "the value is foo" iex(3)> "the value is #{foo}" "the value is 2" iex(4)>
  59. iex(1)> math_problem = quote do: 1 + 2 {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex(2)>
  60. iex(1)> math_problem = quote do: 1 + 2 {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex(2)> Macro.to_string(math_problem)
  61. iex(1)> math_problem = quote do: 1 + 2 {:+, [context:

    Elixir, import: Kernel], [1, 2]} iex(2)> Macro.to_string(math_problem) "1 + 2" iex(3)>
  62. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that"
  63. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that"
  64. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that"
  65. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)>
  66. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)>
  67. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [...], [{:==, [...], [{:+, [...], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)>
  68. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [...], [{:==, [...], [{:+, [...], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)>
  69. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [...], [{:==, [...], [{:+, [...], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)>
  70. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [...], [{:==, [...], [{:+, [...], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)>
  71. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)>
  72. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)> Macro.to_string(foo)
  73. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)> Macro.to_string(foo) "if(1 + 2 == 3) do\n \"this\"\nelse\n \"that\"\nend"
  74. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)> Macro.to_string(foo) |> IO.puts
  75. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)> Macro.to_string(foo) |> IO.puts if(1 + 2 == 3) do "this" else "that" end :ok
  76. iex(1)> foo = quote do: if 1 + 2 ==

    3, do: "this", else: "that" {:if, [context: Elixir, import: Kernel], [{:==, [context: Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]}, [do: "this", else: "that"]]} iex(2)> Macro.to_string(foo) |> IO.puts if(1 + 2 == 3) do "this" else "that" end :ok
  77. defmacro unquote(:.)(left, right), do: error!([left, right]) ... defmacrop error!(args) do

    quote do _ = unquote(args) message = "Elixir's special forms are expanded by the compiler and must not be invoked directly" :erlang.error(RuntimeError.exception(message)) end end
  78. defmacro unquote(:.)(left, right), do: error!([left, right]) ... defmacrop error!(args) do

    quote do _ = unquote(args) message = "Elixir's special forms are expanded by the compiler and must not be invoked directly" :erlang.error(RuntimeError.exception(message)) end end
  79. defmacro unquote(:.)(left, right), do: error!([left, right]) ... defmacrop error!(args) do

    quote do _ = unquote(args) message = "Elixir's special forms are expanded by the compiler and must not be invoked directly" :erlang.error(RuntimeError.exception(message)) end end
  80. iex(1)> quote do: 1 + 2 == 3 {:==, [context:

    Elixir, import: Kernel], [{:+, [context: Elixir, import: Kernel], [1, 2]}, 3]} iex(2)>
  81. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    case condition do true -> do_clause _ -> else_clause end end end
  82. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case condition do true -> do_clause _ -> else_clause end end end end
  83. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case condition do true -> do_clause _ -> else_clause end end end end
  84. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case condition do true -> do_clause _ -> else_clause end end end end
  85. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case condition do true -> do_clause _ -> else_clause end end end end
  86. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case unquote(condition) do true -> do_clause _ -> else_clause end end end end
  87. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case unquote(condition) do true -> do_clause _ -> else_clause end end end end
  88. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case unquote(condition) do true -> unquote(do_clause) _ -> unquote(else_clause) end end end end
  89. iex(1)> require MyMacros MyMacros iex(2)> MyMacros.my_if 1 + 2 ==

    3, do: "this", else: "that" "this" iex(3)>
  90. iex(1)> require MyMacros MyMacros iex(2)> MyMacros.my_if 1 + 2 ==

    3, do: "this", else: "that" "this" iex(3)> MyMacros.my_if 1 + 2 == 10, do: "this", else: "that"
  91. iex(1)> require MyMacros MyMacros iex(2)> MyMacros.my_if 1 + 2 ==

    3, do: "this", else: "that" "this" iex(3)> MyMacros.my_if 1 + 2 == 10, do: "this", else: "that" "that" iex(4)>
  92. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case unquote(condition) do true -> unquote(do_clause) _ -> unquote(else_clause) end end end end
  93. defmodule MyMacros do defmacro my_if(condition, do: do_clause, else: else_clause) do

    quote do case unquote(condition) do true -> unquote(do_clause) _ -> unquote(else_clause) end end end end
  94. defmodule MyFunctions do def my_if(condition, do: do_clause, else: else_clause) do

    case condition do true -> do_clause _ -> else_clause end end end
  95. defmodule MyFunctions do def my_if(condition, do: do_clause, else: else_clause) do

    case condition do true -> do_clause _ -> else_clause end end end
  96. iex(1)> require MyFunctions MyFunctions iex(2)> MyFunctions.my_if 1 + 2 ==

    3, do: "this", else: "that" "this" iex(3)> MyFunctions.my_if 1 + 2 == 10, do: "this", else: "that"
  97. iex(1)> require MyFunctions MyFunctions iex(2)> MyFunctions.my_if 1 + 2 ==

    3, do: "this", else: "that" "this" iex(3)> MyFunctions.my_if 1 + 2 == 10, do: "this", else: "that" "that" iex(4)>
  98. iex(1)> require MyFunctions MyFunctions iex(2)> MyFunctions.my_if 1 + 2 ==

    3, do: IO.puts("this"), else: IO.puts("that") this that :ok iex(3)>
  99. iex(1)> require MyFunctions MyFunctions iex(2)> MyFunctions.my_if 1 + 2 ==

    3, do: IO.puts("this"), else: IO.puts("that") this that :ok iex(3)> MyFunctions.my_if 1 + 2 == 10, do: IO.puts("this"), else: IO.puts("that")
  100. iex(1)> require MyFunctions MyFunctions iex(2)> MyFunctions.my_if 1 + 2 ==

    3, do: IO.puts("this"), else: IO.puts("that") this that :ok iex(3)> MyFunctions.my_if 1 + 2 == 10, do: IO.puts("this"), else: IO.puts("that") this that :ok
  101. iex(1)> require MyMacros MyMacros iex(2)> MyMacros.my_if 1 + 2 ==

    3, do: IO.puts("this"), else: IO.puts("that")
  102. iex(1)> require MyMacros MyMacros iex(2)> MyMacros.my_if 1 + 2 ==

    3, do: IO.puts("this"), else: IO.puts("that") this :ok iex(3)>
  103. iex(1)> require MyMacros MyMacros iex(2)> MyMacros.my_if 1 + 2 ==

    3, do: IO.puts("this"), else: IO.puts("that") this :ok iex(3)> MyMacros.my_if 1 + 2 == 10, do: IO.puts("this"), else: IO.puts("that")
  104. iex(1)> require MyMacros MyMacros iex(2)> MyMacros.my_if 1 + 2 ==

    3, do: IO.puts("this"), else: IO.puts("that") this :ok iex(3)> MyMacros.my_if 1 + 2 == 10, do: IO.puts("this"), else: IO.puts("that") that :ok
  105. defmodule MyMacros do defmacro pretty_math({:+, _meta, [left, right]} = ast)

    do quote do IO.puts """ #{unquote(left)} + #{unquote(right)} --- #{unquote(ast)} """ end end end
  106. defmodule MyMacros do defmacro pretty_math({:+, _meta, [left, right]} = ast)

    do quote do IO.puts """ #{unquote(left)} + #{unquote(right)} --- #{unquote(ast)} """ end end end
  107. defmodule MyMacros do defmacro pretty_math({:+, _meta, [left, right]} = ast)

    do quote do IO.puts """ #{unquote(left)} + #{unquote(right)} --- #{unquote(ast)} """ end end end
  108. defmodule MyMacros do defmacro pretty_math({:+, _meta, [left, right]} = ast)

    do quote do IO.puts """ #{unquote(left)} + #{unquote(right)} --- #{unquote(ast)} """ end end end
  109. defmodule MyMacros do defmacro pretty_math({:+, _meta, [left, right]} = ast)

    do quote do IO.puts """ #{unquote(left)} + #{unquote(right)} --- #{unquote(ast)} """ end end end