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

Deep Dive into Elixir

Deep Dive into Elixir

Doing a deep dive into one of my favourite abstractions in Elixir.

Jonathan Yeong

September 04, 2020
Tweet

More Decks by Jonathan Yeong

Other Decks in Programming

Transcript

  1. “This operator introduces the expression on the left-hand side as

    the first argument to the function call on the right-hand side.” @jonoyeong | ElixirConf 2020
  2. String.downcase(String.reverse("DLROW OLLEH”)) # "hello world” @jonoyeong | ElixirConf 2020 "DLROW

    OLLEH" |> String.reverse() |> String.downcase() # "hello world”
  3. String.downcase(String.reverse("DLROW OLLEH”)) # "hello world” @jonoyeong | ElixirConf 2020 "DLROW

    OLLEH" |> String.reverse() |> String.downcase() # "hello world”
  4. How is |> defined? Where does it specify the position

    of the argument? How does it nest/call functions in the pipeline? @jonoyeong | ElixirConf 2020
  5. defmacro left |> right do [{h, _} | t] =

    Macro.unpipe({:|>, [], [left, right]}) fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, h, t) end @jonoyeong | ElixirConf 2020
  6. quote do: "hello" # "hello" quote do: :world # :world

    quote do: 1+1 # {:+, [], [1, 1]} quote do: div(a, b) # {:div, [], [{:a, [], Elixir}, {:b, [], Elixir}]} @jonoyeong | ElixirConf 2020
  7. a = 10 b = 2 quote do div(a,b) end

    # {:div, [], [{:a, [], Elixir}, {:b, [], Elixir}]} quote do div(unquote(a), unquote(b)) end # {:div, [], [10, 2]} @jonoyeong | ElixirConf 2020 a = 10 b = 2 quote do div(a,b) end # {:div, [], [{:a, [], Elixir}, {:b, [], Elixir}]}
  8. a = 10 b = 2 quote do div(a,b) end

    # {:div, [], [{:a, [], Elixir}, {:b, [], Elixir}]} quote do div(unquote(a), unquote(b)) end # {:div, [], [10, 2]} @jonoyeong | ElixirConf 2020 quote do div(unquote(a), unquote(b)) end # {:div, [], [10, 2]}
  9. Code.eval_quoted(quote do div(unquote(a), unquote(b)) end) # {5, []} Code.eval_quoted(quote do

    a=1 end) # {1, [{{:a, Elixir}, 1}]} Code.eval_quoted(quote do div(a, b) end) # ** (CompileError) nofile:1: undefined function a/0 @jonoyeong | ElixirConf 2020 Code.eval_quoted(quote do div(unquote(a), unquote(b)) end) # {5, []} Code.eval_quoted(quote do a=1 end) # {1, [{{:a, Elixir}, 1}]}
  10. Code.eval_quoted(quote do div(unquote(a), unquote(b)) end) # {5, []} Code.eval_quoted(quote do

    a=1 end) # {1, [{{:a, Elixir}, 1}]} Code.eval_quoted(quote do div(a, b) end) # ** (CompileError) nofile:1: undefined function a/0 @jonoyeong | ElixirConf 2020 Code.eval_quoted(quote do div(a, b) end) # ** (CompileError) nofile:1: undefined function a/0
  11. Macro.to_string({:+, [], [1, 1]} # "1 + 1” Macro.to_string(quote do

    div(unquote(a), unquote(b)) end) # "div(10, 2)" @jonoyeong | ElixirConf 2020
  12. "DLROW OLLEH" |> String.reverse() |> String.downcase() # === Left ===

    # {:|>, [], # [ # "DLROW OLLEH", # {{:., [], [{:__aliases__, [], [:String]}, :reverse]}, # [], []} # ]} # === Right === # {{:., [], [{:__aliases__, [], [:String]}, :downcase]}, [], # []} @jonoyeong | ElixirConf 2020
  13. quote do: "DLROW OLLEH" |> String.reverse() |> String.downcase() {:|>, [],

    [ {:|>, [], [ "DLROW OLLEH", {{:., [], [{:__aliases__, [alias: false], [:String]}, :reverse]}, [], []} ]}, {{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], []} ]} @jonoyeong | ElixirConf 2020
  14. quote do: "DLROW OLLEH" |> String.reverse() |> String.downcase() {:|>, [],

    [ {:|>, [], [ "DLROW OLLEH", {{:., [], [{:__aliases__, [alias: false], [:String]}, :reverse]}, [], []} ]}, {{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], []} ]} @jonoyeong | ElixirConf 2020 {:|>, [], [ "DLROW OLLEH", {{:., [], [{:__aliases__, [alias: false], [:String]}, :reverse]}, [], []} ]},
  15. quote do: "DLROW OLLEH" |> String.reverse() |> String.downcase() {:|>, [],

    [ {:|>, [], [ "DLROW OLLEH", {{:., [], [{:__aliases__, [alias: false], [:String]}, :reverse]}, [], []} ]}, {{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], []} ]} @jonoyeong | ElixirConf 2020 {{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], []}
  16. How is |> defined? Where does it specify the position

    of the argument? How does it nest/call functions in the pipeline? @jonoyeong | ElixirConf 2020
  17. How is |> defined? Where does it specify the position

    of the argument? How does it nest/call functions in the pipeline? @jonoyeong | ElixirConf 2020 ✅
  18. defmacro left |> right do [{h, _} | t] =

    Macro.unpipe({:|>, [], [left, right]}) fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, h, t) end @jonoyeong | ElixirConf 2020 [{h, _} | t] = Macro.unpipe({:|>, [], [left, right]}) fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, h, t) end
  19. [{h, _} | t] = Macro.unpipe({:|>, [], [left, right]}) @jonoyeong

    | ElixirConf 2020 Macro.unpipe({:|>, [], [left, right]})
  20. def unpipe(expr) do :lists.reverse(unpipe(expr, [])) end defp unpipe({:|>, _, [left,

    right]}, acc) do unpipe(right, unpipe(left, acc)) end defp unpipe(other, acc) do [{other, 0} | acc] end @jonoyeong | ElixirConf 2020
  21. def unpipe(expr) do :lists.reverse(unpipe(expr, [])) end defp unpipe({:|>, _, [left,

    right]}, acc) do unpipe(right, unpipe(left, acc)) end defp unpipe(other, acc) do [{other, 0} | acc] end @jonoyeong | ElixirConf 2020 defp unpipe(other, acc) do [{other, 0} | acc] end
  22. Macro.unpipe(quote do: 100 |> div(5) |> div(2)) # [{100, 0},

    {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] @jonoyeong | ElixirConf 2020
  23. How is |> defined? Where does it specify the position

    of the argument? How does it nest/call functions in the pipeline? @jonoyeong | ElixirConf 2020 ✅
  24. How is |> defined? Where does it specify the position

    of the argument? How does it nest/call functions in the pipeline? @jonoyeong | ElixirConf 2020 ✅ ✅
  25. defmacro left |> right do [{h, _} | t] =

    Macro.unpipe({:|>, [], [left, right]}) fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, h, t) end @jonoyeong | ElixirConf 2020 fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, h, t) end
  26. fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos)

    end @jonoyeong | ElixirConf 2020
  27. def pipe(expr, {op, line, args} = op_args, integer) when is_list(args)

    do cond do is_atom(op) and Identifier.unary_op(op) != :error -> raise ArgumentError, "cannot pipe #{to_string(expr)} into #{to_string(op_args)}, " <> "the #{to_string(op)} operator can only take one argument" is_atom(op) and Identifier.binary_op(op) != :error -> raise ArgumentError, "cannot pipe #{to_string(expr)} into #{to_string(op_args)}, " <> "the #{to_string(op)} operator can only take two arguments" true -> {op, line, List.insert_at(args, integer, expr)} end end @jonoyeong | ElixirConf 2020
  28. def pipe(expr, {op, line, args} = op_args, integer) when is_list(args)

    do cond do is_atom(op) and Identifier.unary_op(op) != :error -> raise ArgumentError, "cannot pipe #{to_string(expr)} into #{to_string(op_args)}, " <> "the #{to_string(op)} operator can only take one argument" is_atom(op) and Identifier.binary_op(op) != :error -> raise ArgumentError, "cannot pipe #{to_string(expr)} into #{to_string(op_args)}, " <> "the #{to_string(op)} operator can only take two arguments" true -> {op, line, List.insert_at(args, integer, expr)} end end @jonoyeong | ElixirConf 2020 true -> {op, line, List.insert_at(args, integer, expr)}
  29. Macro.pipe(100, {:div, [], [5]}, 0) # {:div, [], [100, 5]}

    Macro.pipe({:div, [], [100, 5]}, {:div, [], [2]}, 0) # {:div, [], [{:div, [], [100, 5]}, 2]} @jonoyeong | ElixirConf 2020
  30. def left - right do :erlang.-(left, right) end def flatten(list)

    do :lists.flatten(list) end def mkdir(path) do :file.make_dir(IO.chardata_to_string(path)) end @jonoyeong | ElixirConf 2020
  31. i :lists # Term # :lists # Data type #

    Atom # Description # Call :lists.module_info() to access metadata. # Raw representation # :"lists" # Reference modules # Module, Atom # Implemented protocols # IEx.Info, Inspect, List.Chars, String.Chars @jonoyeong | ElixirConf 2020
  32. i :hello # Term # :hello # Data type #

    Atom # Reference modules # Atom # Implemented protocols # IEx.Info, Inspect, List.Chars, String.Chars @jonoyeong | ElixirConf 2020
  33. “[Open Telecom Platform] is a set of Erlang libraries and

    design principles providing middleware to develop systems” Erlang.com @jonoyeong | ElixirConf 2020
  34. foldl(Fun, Acc0, List) -> Acc1 lists:foldl(fun(X, Sum) -> X +

    Sum end, 0, [1,2,3,4,5]). % 15 @jonoyeong | ElixirConf 2020
  35. fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos)

    end @jonoyeong | ElixirConf 2020
  36. @jonoyeong | ElixirConf 2020 100 |> div(5) |> div(2) #

    [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, 100, [{{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]) # {:div, [], [{:div, [], [100, 5]}, 2]} Macro.to_string({:div, [], [{:div, [], [100, 5]}, 2]}) # "div(div(100, 5), 2)"
  37. @jonoyeong | ElixirConf 2020 100 |> div(5) |> div(2) #

    [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, 100, [{{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]) # {:div, [], [{:div, [], [100, 5]}, 2]} Macro.to_string({:div, [], [{:div, [], [100, 5]}, 2]}) # "div(div(100, 5), 2)"
  38. @jonoyeong | ElixirConf 2020 100 |> div(5) |> div(2) #

    [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, 100, [{{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]) # {:div, [], [{:div, [], [100, 5]}, 2]} Macro.to_string({:div, [], [{:div, [], [100, 5]}, 2]}) # "div(div(100, 5), 2)"
  39. @jonoyeong | ElixirConf 2020 100 |> div(5) |> div(2) #

    [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end :lists.foldl(fun, 100, [{{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]) # {:div, [], [{:div, [], [100, 5]}, 2]} Macro.to_string({:div, [], [{:div, [], [100, 5]}, 2]}) # "div(div(100, 5), 2)"
  40. How is |> defined? Where does it specify the position

    of the argument? How does it nest/call functions in the pipeline? @jonoyeong | ElixirConf 2020 ✅ ✅
  41. How is |> defined? Where does it specify the position

    of the argument? How does it nest/call functions in the pipeline? @jonoyeong | ElixirConf 2020 ✅ ✅ ✅
  42. @jonoyeong | ElixirConf 2020 2 |> Integer.to_string(10, ...) # “1010”

    defp unpipe(ast, acc, %Macro.Env{line: line, file: file}) do case find_pos(ast) do {:ok, new_ast, pos} -> [{new_ast, pos} | acc] {:error, {:already_found, _, _}} -> raise CompileError, file: file, line: line, description: "Doubled placeholder in #{Macro.to_string(ast)}" end end Magritte.ex defp unpipe(ast, acc, %Macro.Env{line: line, file: file}) do case find_pos(ast) do {:ok, new_ast, pos} -> [{new_ast, pos} | acc] end end 2 |> Integer.to_string(10, ...) # “1010”