Share
Embed
Transcript
36 slides
-
Me • Originally from SF Bay Area • Writing Python
since 2001 • Mostly backend systems + SAAS architecture + (micro)services • Plenty of Django • Plenty of horrible codebases • Looking for a better way -
What we'll cover • Elixir as a functional language •
Data types • Pattern matching • Runtime/processes -
Why Elixir? • Scalability / concurrency • Distributed system /
microservices usually requires tooling, systems and lots of management • Distributed programming built into the language via Erlang/BEAM • Performance • Many languages aren't that good at resource utilization: Python, Ruby, JavaScript/Node • Other more performant languages are a bit too low-level: C, Go, Rust • Functional • Immutable state means concurrency is easier • Immutable state means is harder to write big balls of mud -
Procedural or OO 1 def calculate_taxes(username, year=2016): 2 try: 3
person = PeopleService(username) 4 except PeopleService.NotFoudException: 5 raise e 6 7 orders = OrdersService.get_orders_for_customer(person) 8 if not orders: 9 return 10 11 tax = calculate_sales_tax(orders, year) 12 13 return prepare_filing(tax) -
vs. Functional 1 defmodule TaxService do 2 3 def calculate_taxes(username,
year // 2016) do 4 {:ok, person} = PeopleService(username) 5 6 person 7 |> OrderService.for_customer 8 |> calculate_taxes(year) 9 |> prepare_filing 10 end 11 12 end -
$ ls | > grep "\.exs" | > grep -v
_test We already think functionally -
a = [1, 2, 3] func(a) # what is a?
Removing mutation -
a = [1, 2, 3] func(a) a == [1, 2,
3] true # always In Elixir a won't change -
a = [1, 2, 3] a = func(a) # a
has been re-bound # to a new list Rebinding isn't mutation -
Elixir types 1, 100_000, 0xDEADBEEF, 0b10111 Integer 1.0, 0.31415e1, 31415.0e-5
Float :ok, :is_binary?, :srsly! Atom 1..10 Ranges ~r/[aeiou]/, ~r{[aeiou]} RegEx {:ok, file}, {:error, _} Tuple [1, 2, 3], ['a', 'b'] List %{ name: "bz", state: "CO" } %{ "CO" => "Colorado" } Map true, false, nil Truth <<1 :: size(4), 15 :: size(4)>> # 0001 1111 Binaries -
Elixir Strings • Single quoted • List of integers •
iex represents this as a string if each char is printable • Double quoted • Sequence of UTF-8 bytes • Each byte is a binary -
Char lists iex(102)> [67, 97, 116] 'Cat' iex(103)> is_list 'hello
world' true iex(104)> List.to_tuple 'Cat' {67, 97, 116} iex(107)> 'CaӠ' [67, 97, 19971] -
Strings iex(128)> "C" == 'C' false iex(135)> [67] 'C' iex(136)>
"C" == << 67 :: size(8) >> true iex(138)> String.upcase("cat") "CAT" iex(146)> String.length("CaӠ") 3 iex(147)> byte_size("CaӠ") 5 -
iex(5)> a = 1 1 iex(6)> 1 = 1 1
iex(7)> 1 = a 1 merely matching up the left and right side of "=" -
1 = a doesn't work in other languages >>> a
= 1 >>> 1 = a File "<stdin>", line 1 SyntaxError: can't assign to literal >>> 1 = 1 File "<stdin>", line 1 SyntaxError: can't assign to literal >>> irb(main):001:0> a = 1 => 1 irb(main):002:0> 1 = a SyntaxError: (irb):2: syntax error, unexpected '=', expecting end-of-input 1 = a ^ from /usr/local/bin/irb:11:in `<main>' irb(main):003:0> 1 = 1 SyntaxError: (irb):3: syntax error, unexpected '=', expecting end-of-input 1 = 1 ^ from /usr/local/bin/irb:11:in `<main>' -
1 = a cannot work in Elixir too! a must
be defined before matching iex(1)> 1 = a ** (CompileError) iex:1: undefined function a/0 iex(1)> a = 1 1 iex(2)> 1 = a 1 -
iex(8)> list = [1, 2, 3] [1, 2, 3] iex(9)>
[a, 2, b] = list [1, 2, 3] iex(10)> a 1 iex(11)> b 3 Matching with lists -
iex(14)> list = [1, 2, 3] [1, 2, 3] iex(15)>
[a, 1, b] = list ** (MatchError) no match of right hand side value: [1, 2, 3] Mis-matching with lists -
iex(14)> list = [1, 2, 3] [1, 2, 3] iex(15)>
[1, _, b] = list [1, 2, 3] Ignoring during matching -
iex(2)> me = %{ name: "Brian", age: 43 } %{age:
43, name: "Brian"} iex(3)> %{name: aname} = me %{age: 43, name: "Brian"} iex(6)> aname "Brian" -
iex(7)> %{name: "Brian"} = me %{age: 43, name: "Brian"} iex(8)>
%{name: "Joe"} = me ** (MatchError) no match of right hand side value: %{age: 43, name: "Brian"} Mis-matching with maps -
Matching based on function arguments 1 defmodule Fib do 2
def fib(1), do: 1 3 def fib(2), do: 1 4 def fib(n), do: fib(n-1) + fib(n-2) 5 end -
Matching based on function arity 1 defmodule Calculator do 2
3 def inc(n) do 4 n + 1 5 end 6 7 def inc(n, m) do 8 n + m 9 end 10 11 end -
Simple GenServer defmodule Calculator.Server do use GenServer # API def
start_link do GenServer.start_link(__MODULE__, nil, name: :calculator) end def divide(n, m) do GenServer.call(:calculator, {:divide, n, m}) end # end API -
Simple GenServer # Server callbacks def init(nil) do {:ok, nil}
end def handle_call({:multiply, n, m}, _from, _state) do { :reply, n * m, nil } end end -
Running our Server iex(1)> Calculator.Server.start_link {:ok, #PID<0.119.0>} iex(2)> Calculator.Server.divide(10, 2)
5.0 iex(3)> Calculator.Server.divide(10, 0) ** (EXIT from #PID<0.117.0>) an exception was raised: <snip> State: nil nil iex(1)> iex(2)> Calculator.Server.divide(10, 2) ** (exit) exited in: GenServer.call(:calculator, {:divide, 10, 2}, 5000) ** (EXIT) no process (elixir) lib/gen_server.ex:596: GenServer.call/3 -
Running with Supervisor 1 defmodule Calculator.Supervisor do 2 use Supervisor
3 4 def start_link do 5 Supervisor.start_link(__MODULE__, []) 6 end 7 8 def init(_) do 9 children = [ 10 worker(Calculator.Server, []) 11 ] 12 supervise(children, strategy: :one_for_one) 13 end 14 end -
Running with Supervisor iex(1)> Calculator.Supervisor.start_link {:ok, #PID<0.108.0>} iex(2)> Calculator.divide(10, 2)
5.0 iex(3)> Calculator.divide(10, 0) ** (RuntimeError) Cannot divide by zero (calculator) lib/calculator.ex:6: Calculator.divide/2 iex(3)> Calculator.divide(10, 2) 5.0 -
Resources Introduction to Elixir & Phoenix https://youtu.be/cKeJJldQRgI GenServer/Supervisor gists https://git.io/calculator
https://git.io/supervisor Programming Elixir https://pragprog.com/book/elixir/programming-elixir Docs https://elixirschool.com http://elixir-lang.org/getting-started/introduction.html