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

Elixir in Elixir

Elixir in Elixir

1433d0176e764f9ce48f70673baa72e8?s=128

Jay Hayes

March 15, 2018
Tweet

Transcript

  1. Metaprogramming Programs that Write Programs

  2. InElixir Elixir

  3. ElixirConf Orlando!

  4. How to give the talk

  5. 1. Set up the Topic

  6. How much?

  7. It depends™

  8. What is “Elixir”?

  9. Basics + STDLIB

  10. of Elixir is written in Elixir 78%

  11. of Elixir is written in Elixir 78% 87%

  12. Compare to Ruby’s 44%

  13. Numbers? Meh

  14. if

  15. def

  16. |>

  17. Metaprogramming

  18. 2. Introductions

  19. https://iamvery.com — @iamvery Jay Hayes

  20. None
  21. None
  22. None
  23. We’re hiring!

  24. 3. Definitions

  25. Metaprogramming

  26. None
  27. None
  28. Code generation

  29. Definition, plz

  30. Metaprogramming

  31. programming

  32. Work on Data

  33. 4 + 2

  34. 4 + 2 data

  35. 4 + 2 data func

  36. +(4, 2)

  37. Kernel. ( , ) 4 2 +

  38. 4 2 +

  39. 4 2 + # => 6

  40. Meta…

  41. The program is data

  42. Programming the Program

  43. Code generation

  44. 4. Macros & Expressions

  45. Macros

  46. Macros are everywhere

  47. good macros are invisible

  48. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  49. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end macros
  50. What is the data?

  51. What is the data? Program

  52. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  53. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  54. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  55. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  56. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  57. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  58. defmodule ItTest do use ExUnit.Case test “works” do assert It.works?

    end end
  59. Program macro Program ✨ ✨

  60. Program macro Program ✨ ✨

  61. Program macro Program ✨ ✨

  62. Program macro Program Expression Expression ✨ ✨

  63. Quoted expression?

  64. Code Expressed as Data

  65. {:+, [], [1, 2]}

  66. Abstract Syntax Tree

  67. / 1 + 2 3

  68. / 1 + 2 3 / 1 + 2 3

  69. ( ( )) / 1 + 2 3

  70. ( ( )) / 1 + 2 3 func

  71. ( ( )) / 1 + 2 3 func Args

  72. ( ( )) / 1 + 2 3 func Args

  73. 1 / ( 2 + 3 )

  74. / 1 + 2 3

  75. {: , …, [ , {: , …, [ ,

    ]} / 1 + 2 3
  76. {func, meta, args}

  77. {func, meta, args} +(1, 2)

  78. {:+, [], [1, 2]}

  79. Back to macros…

  80. Macros are Functions

  81. Args are quoted

  82. Args are quoted

  83. (not evaluated)

  84. Expression.match?(:lol > :wat) # => false Expression.match?(:lol = :wat) #

    => true
  85. defmacro match?({:=, _, _}) do true end defmacro match?(_expr) do

    false end
  86. Compile time

  87. Expression.match?(:lol > :wat) Expression.match?(:lol = :wat)

  88. false true

  89. Must return quoted

  90. Must return quoted

  91. Code generation

  92. defmacro badmatch do {:=, [], [:lol, :wat]} end

  93. Expression.badmatch # (MatchError)

  94. :lol = :wat # (MatchError)

  95. Program macro Program Expression Expression ✨ ✨

  96. 5. Quoting

  97. {:+, [], [1, {:-, [], [2, 3]}]}

  98. Quote/2

  99. Easier to grok

  100. Return AST of Expression

  101. Return AST of Expression

  102. {func, meta, args}

  103. quote do :lol = :wat end

  104. {:=, [], [:lol, :wat]}

  105. quote do 1 / (2 + 3) end

  106. {:/, [], [1, {:+, [2, 3]}

  107. {:/, [], [1, {:+, [2, 3]}

  108. ]} {:/, [], [1, {:+, [2, 3]}

  109. Why “quote”?

  110. Like strings…

  111. Sort of…

  112. “1+1”

  113. eval “1+1”

  114. eval “1+1” # => 2

  115. quote(do: 1+1)

  116. expr = quote(do: 1+1) Code.eval_quoted(expr)

  117. expr = quote(do: 1+1) Code.eval_quoted(expr) # => {2, []}

  118. Bonus:Syntax✔

  119. Recursive

  120. Terminal Values

  121. Quoted Literals

  122. quote do: “foo” # “foo” quote do: :foo # :foo

    quote do: 1 # 1 quote do: [1] # [1] quote do: {1,2} # {1,2} quote do: [a: 1] # [a: 1]
  123. 6. Unquoting

  124. Value Injection

  125. Unquote/1

  126. Strings?

  127. Like string interpolation

  128. Sort of…

  129. Inject value into quoted string

  130. name = “Sue” “hi name” # => “hi name”

  131. name = “Sue” “hi #{name}” # => “hi Sue”

  132. Inject value into quoted Expression

  133. num = 42 expr = quote do: 1 + num

    # => {:+, [], [1, {:num, [], :Elixir}]}
  134. num = 42 expr = quote do: 1 + num

    # => {:+, [], [1, {:num, [], :Elixir}]} Code.eval_quoted(expr) # ERROR! `num` is undefined
  135. num = 42 expr = quote do: 1 + unquote(num)

    # => {:+, [], [1, 42]}
  136. num = 42 expr = quote do: 1 + unquote(num)

    # => {:+, [], [1, 42]} Code.eval_quoted(expr) # => {43, []}
  137. num = 42 expr = “1 + #{num}” # =>

    “1 + 42” eval expr # => 43
  138. Inject value into quoted Expression

  139. “It’s already quoted”

  140. Expression Injection

  141. one = quote do: 1 + 1 # => {:+,

    [], [1, 1]} two = quote do: 2 + 2 # => {:+, [], [2, 2]} sum = quote do: unquote(one) + unquote(two) # => {:+, [], [ # {:+, [], [1, 1]}, # {:+, [], [2, 2]} # ]}
  142. Code.eval_quoted(sum) # => {6, []}

  143. 7. Write a Macro

  144. if

  145. if foo > 0 do IO.puts(“Pass!”) else IO.puts(“Fail.”) end

  146. if(…, do: …, else: …)

  147. if(…, [do: …, else: …])

  148. if(…, [do: …, else: …]) cond

  149. if(…, [do: …, else: …]) cases cond

  150. Truthy & Falsey

  151. false || nil Falsey

  152. Anything else Truthy

  153. case … do x when x in [false, nil] ->

    … _ -> … end
  154. case … do x when x in [false, nil] ->

    … _ -> … end cond
  155. case … do x when x in [false, nil] ->

    … _ -> … end cond cases
  156. defmacro if(c, do: tc, else: fc) do quote do case

    unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end
  157. defmacro if(c, do: tc, else: fc) do quote do case

    unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cond
  158. defmacro if(c, do: tc, else: fc) do quote do case

    unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cases
  159. defmacro if(c, do: tc, else: fc) do quote do case

    unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end
  160. defmacro if(c, do: tc, else: fc) do quote do case

    unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cond
  161. defmacro if(c, do: tc, else: fc) do quote do case

    unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cases
  162. defmacro if(c, do: tc, else: fc) do quote do case

    unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end
  163. if do else end IO.puts(“Pass!”) IO.puts(“Fail.”) foo > 0

  164. case do x when x in [false, nil] -> _

    -> end IO.puts(“Pass!”) IO.puts(“Fail.”) foo > 0
  165. 8. Wrap Up with Guidance

  166. When to macro…

  167. #1 Don’t

  168. Write functions

  169. import String defmacro adds(a, b) do quote do to_integer(unquote(a)) +

    to_integer(unquote(b)) end end
  170. import String defmacro adds(a, b) do quote do to_integer(unquote(a)) +

    to_integer(unquote(b)) end end
  171. import String def adds(a, b) do to_integer(a) + to_integer(b) end

  172. Prefer direct solutions

  173. Decision based on args

  174. Transformation

  175. Less error-prone

  176. #2 Constants

  177. defmodule Conversions do defmacro seconds_per_minute, do: 60 defmacro minutes_per_hour, do:

    60 end
  178. h/t module attrs @second_per_minute

  179. #3 DSL

  180. html do body do div class: “content” do p “Engaging

    content.” end end end
  181. scope “/”, HelloWeb do pipe_through :browser get “/”, PageController, :index

    end
  182. #4 Hide details

  183. test “works” do assert It.works? end

  184. #5 Reduce boilerplate

  185. Language extension

  186. defmodule Lolwat do def lol do :wat end end

  187. :elixir_module.compile(Lolwat, {:def, [context: Elixir, import: Kernel], [{:lol, [context: Elixir], Elixir},

    [do: :wat]]}, [], __ENV__)
  188. :elixir_module.compile(Lolwat, {:def, [context: Elixir, import: Kernel], [{:lol, [context: Elixir], Elixir},

    [do: :wat]]}, [], %{__struct__: Macro.Env, aliases: [], context: nil, context_modules: [Mex], export_vars: nil, file: "iex", function: nil, functions: [Mex: [expand_all: 2], "IEx.Helpers": [c: 1, c: 2, cd: 1, clear: 0, flush: 0, h: 0, i: 1, l: 1, ls: 0, ls: 1, nl: 1, nl: 2, pid: 1, pid: 3, pwd: 0, r: 1, recompile: 0, respawn: 0, v: 0, v: 1], Kernel: [!=: 2, !==: 2, *: 2, +: 1, +: 2, ++: 2, -: 1, -: 2, --: 2, /: 2, <: 2, <=: 2, ==: 2, ===: 2, =~: 2, >: 2, >=: 2, abs: 1, apply: 2, apply: 3, binary_part: 3, bit_size: 1, byte_size: 1, div: 2, elem: 2, exit: 1, function_exported?: 3, get_and_update_in: 3, get_in: 2, hd: 1, inspect: 1, inspect: 2, is_atom: 1, is_binary: 1, is_bitstring: 1, is_boolean: 1, is_float: 1, is_function: 1, is_function: 2, is_integer: 1, is_list: 1, is_map: 1, is_number: 1, is_pid: 1, is_port: 1, is_reference: 1, is_tuple: 1, length: 1, macro_exported?: 3, make_ref: 0, map_size: 1, max: 2, min: 2, node: 0, node: 1, not: 1, pop_in: 2, put_elem: 3, put_in: 3, rem: 2, round: 1, self: 0, send: 2, spawn: 1, spawn: 3, spawn_link: 1, spawn_link: 3, spawn_monitor: 1, spawn_monitor: 3, struct: 1, struct: 2, struct!: 1, struct!: 2, throw: 1, tl: 1, trunc: 1, tuple_size: 1, update_in: 3]], lexical_tracker: nil, line: 2, macro_aliases: [], macros: [Mex: [mex: 1], "IEx.Helpers": [b: 1, h: 1, import_file: 1, import_file: 2, import_file_if_available: 1, import_if_available: 1, import_if_available: 2, s: 1, t: 1], Kernel: [!: 1, &&: 2, ..: 2, <>: 2, @: 1, alias!: 1, and: 2, binding: 0, binding: 1, def: 1, def: 2, defdelegate: 2, defexception: 1, defimpl: 2, defimpl: 3, defmacro: 1, defmacro: 2, defmacrop: 1, defmacrop: 2, defmodule: 2, defoverridable: 1, defp: 1, defp: 2, defprotocol: 2, defstruct: 1, destructure: 2, get_and_update_in: 2, if: 2, in: 2, is_nil: 1, match?: 2, or: 2, pop_in: 1, put_in: 2, raise: 1, raise: 2, reraise: 2, reraise: 3, sigil_C: 2, sigil_D: 2, sigil_N: 2, sigil_R: 2, sigil_S: 2, sigil_T: 2, sigil_W: 2, sigil_c: 2, sigil_r: 2, sigil_s: 2, sigil_w: 2, to_char_list: 1, to_charlist: 1, to_string: 1, unless: 2, update_in: 2, use: 1, use: 2, var!: 1, var!: 2, |>: 2, ||: 2]], module: nil, requires: [IEx.Helpers, Kernel, Kernel.Typespec, Mex], vars: []})
  189. None
  190. None
  191. 9. Self Promotion

  192. bit.ly/ElixirMeta2

  193. Questions? @iamvery

  194. With love,