Slide 1

Slide 1 text

Deep Dive into Elixir Well really, just one abstraction @jonoyeong | ElixirConf 2020

Slide 2

Slide 2 text

Excited to be here @jonoyeong | ElixirConf 2020

Slide 3

Slide 3 text

Pipe Operator @jonoyeong | ElixirConf 2020

Slide 4

Slide 4 text

@jonoyeong | ElixirConf 2020 Documentation

Slide 5

Slide 5 text

“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

Slide 6

Slide 6 text

@jonoyeong | ElixirConf 2020

Slide 7

Slide 7 text

Ruby @jonoyeong | ElixirConf 2020

Slide 8

Slide 8 text

Let’s see it in action @jonoyeong | ElixirConf 2020

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

But how tho? @jonoyeong | ElixirConf 2020

Slide 12

Slide 12 text

Pipeline ??? Nested Function @jonoyeong | ElixirConf 2020

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Going down the rabbit hole @jonoyeong | ElixirConf 2020

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

defmacro left |> right do @jonoyeong | ElixirConf 2020

Slide 17

Slide 17 text

defmacro left |> right do @jonoyeong | ElixirConf 2020 defmacro

Slide 18

Slide 18 text

Quotes @jonoyeong | ElixirConf 2020

Slide 19

Slide 19 text

Macros take & return quoted Expressions @jonoyeong | ElixirConf 2020

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

How do we inject code? @jonoyeong | ElixirConf 2020

Slide 22

Slide 22 text

Unquoting @jonoyeong | ElixirConf 2020

Slide 23

Slide 23 text

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}]}

Slide 24

Slide 24 text

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]}

Slide 25

Slide 25 text

Evaluating a quote? @jonoyeong | ElixirConf 2020

Slide 26

Slide 26 text

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}]}

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

The textual representation @jonoyeong | ElixirConf 2020

Slide 29

Slide 29 text

Macro.to_string({:+, [], [1, 1]} # "1 + 1” Macro.to_string(quote do div(unquote(a), unquote(b)) end) # "div(10, 2)" @jonoyeong | ElixirConf 2020

Slide 30

Slide 30 text

Macro Recap @jonoyeong | ElixirConf 2020

Slide 31

Slide 31 text

Macros take quoted expressions @jonoyeong | ElixirConf 2020

Slide 32

Slide 32 text

Macros return quoted expressions @jonoyeong | ElixirConf 2020

Slide 33

Slide 33 text

Macros evaluated during compile time @jonoyeong | ElixirConf 2020

Slide 34

Slide 34 text

@jonoyeong | ElixirConf 2020 defmacro left |> right do

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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]}, [], []} ]},

Slide 38

Slide 38 text

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]}, [], []}

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

[{h, _} | t] = Macro.unpipe({:|>, [], [left, right]}) @jonoyeong | ElixirConf 2020

Slide 43

Slide 43 text

[{h, _} | t] = Macro.unpipe({:|>, [], [left, right]}) @jonoyeong | ElixirConf 2020 Macro.unpipe({:|>, [], [left, right]})

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Macro.unpipe(quote do: 100 |> div(5) |> div(2)) # [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] @jonoyeong | ElixirConf 2020

Slide 47

Slide 47 text

We don’t know how… Yet! @jonoyeong | ElixirConf 2020

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end @jonoyeong | ElixirConf 2020

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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)}

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

I can feel it in my fingers @jonoyeong | ElixirConf 2020

Slide 56

Slide 56 text

:lists.foldl(fun, h, t) @jonoyeong | ElixirConf 2020

Slide 57

Slide 57 text

Atom? What are you doing here? @jonoyeong | ElixirConf 2020

Slide 58

Slide 58 text

Oh this is Erlang @jonoyeong | ElixirConf 2020

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

i :hello # Term # :hello # Data type # Atom # Reference modules # Atom # Implemented protocols # IEx.Info, Inspect, List.Chars, String.Chars @jonoyeong | ElixirConf 2020

Slide 62

Slide 62 text

Erlang/OTP @jonoyeong | ElixirConf 2020

Slide 63

Slide 63 text

Erlang/OTP @jonoyeong | ElixirConf 2020

Slide 64

Slide 64 text

“[Open Telecom Platform] is a set of Erlang libraries and design principles providing middleware to develop systems” Erlang.com @jonoyeong | ElixirConf 2020

Slide 65

Slide 65 text

@jonoyeong | ElixirConf 2020

Slide 66

Slide 66 text

Let’s get back to it @jonoyeong | ElixirConf 2020

Slide 67

Slide 67 text

foldl(Fun, Acc0, List) -> Acc1 lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]). % 15 @jonoyeong | ElixirConf 2020

Slide 68

Slide 68 text

:lists.foldl(fun, h, t) @jonoyeong | ElixirConf 2020 (fun, h, t)

Slide 69

Slide 69 text

fun = fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end @jonoyeong | ElixirConf 2020

Slide 70

Slide 70 text

[{h, _} | t] = Macro.unpipe({:|>, [], [left, right]}) @jonoyeong | ElixirConf 2020 [{h, _} | t]

Slide 71

Slide 71 text

All together now @jonoyeong | ElixirConf 2020

Slide 72

Slide 72 text

@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)"

Slide 73

Slide 73 text

@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)"

Slide 74

Slide 74 text

@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)"

Slide 75

Slide 75 text

@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)"

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Why did I do this again? @jonoyeong | ElixirConf 2020

Slide 79

Slide 79 text

Get a better understanding @jonoyeong | ElixirConf 2020

Slide 80

Slide 80 text

Dispel the magic @jonoyeong | ElixirConf 2020

Slide 81

Slide 81 text

@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”

Slide 82

Slide 82 text

Scratch that itch @jonoyeong | ElixirConf 2020

Slide 83

Slide 83 text

Thank you! @jonoyeong