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

Elixir: A Gentle Introduction

Avatar for Alan Voss Alan Voss
November 18, 2016

Elixir: A Gentle Introduction

A talk I'm giving at my company to talk about a new language.

Avatar for Alan Voss

Alan Voss

November 18, 2016
Tweet

Other Decks in Technology

Transcript

  1. Background: Erlang • Developed by Ericsson (1986) • Open Source

    (1998) • Originally used in Telephony Applications • Functional • Everything is a process • Processes send messages to other processes (or themselves) • Processes have a mailbox to receive messages • Fault tolerant • Immutable • Tail-optimized recursion • Pattern matching
  2. Background: BEAM • Virtual Machine environment for Erlang • Utilizes

    “processes” and “threads” ◦ Processes are super light-weight ◦ Not implemented using underlying OS ◦ Processes get preempted by the VM scheduler so that resources aren’t hogged. • Has compilation step ◦ Can also generate a standalone binary • Garbage collection at the individual process level ◦ Doesn’t “Stop The World” • Has many tweaking switches, similar to the JVM • Attempts to keep all CPUs occupied
  3. Enter Elixir • Erlang has funky syntax ◦ Hard to

    learn ◦ Turns people off to the language • Ruby has nice syntax • How about make Erlang look more like Ruby? ◦ Instead of having to learn functional concepts and a funky syntax, can focus on the former. • Invented by José Valim at Plataformatec • Can utilize existing Erlang code • Adds generalized macros, protocols
  4. Functional • Data in -> data out • No concept

    of an object • Everything is a function that accepts data and returns data
  5. Immutable • Something in memory cannot be altered or stomped

    upon • Anytime a change is needed, new memory is allocated • Messages sent to other process are copied to that process’s heap • More deterministic • Easier to reason about ◦ E.g. don’t have to look at other methods to see how they might munge an instance variable
  6. Fault Tolerant • Utilizes Supervision Trees for processes • Can

    detect when processes crash and immediately spin them back up • A process crash doesn’t have to affect any other processes ◦ They can continue as if nothing happened • Lots of 9s in uptime stats are possible: 99.9999999%
  7. Concurrency is easy (or easier, at least) • Actor pattern

    • No more Thread declarations, Mutexes, Semaphores, Synchonizing ◦ Create lots of bugs ◦ Also bottlenecks ◦ Steep learning curve • Spin up a process • Give it a name • Give it an initial state • Let it go off and do its thing • Get the result, if need be • Memory is copied; everything is immutable • Will utilize all cores available to the VM
  8. Hot Swapping (of Code) • To maintain high uptime, processes

    can have versions • New code can be inserted • Upgrade code can be run • Application can resume with new code
  9. Multiple Node (Computers) • Need more computing power? • Add

    another computer, and attach it seamlessly. • The VM has this capability built in
  10. Companies • Ericsson telephony software ◦ 3G and LTE ◦

    Need super high availability • Amazon ◦ SimpleDB, distributed database • Facebook ◦ Parts of their messaging platform • WhatsApp (recently bought by Facebook) • Basho ◦ Riak
  11. Code Layout • Modules • Functions defmodule MathStuff do def

    multiply(x, y), do: x * y def regular_polygon_angles(sides) do multiply(sides - 2, 180) / sides end end
  12. Lists • Actually Linked Lists • Cons operator | for

    prepending to the list • ++ operator for appending to the list ◦ Generally best to use cons and reverse list than ++ iex(1)> my_list = [1,2,3] [1, 2, 3] iex(2)> prepended_zero = [0 | my_list] [0, 1, 2, 3] iex(3)> appended_four = prepended_zero ++ [4] [0, 1, 2, 3, 4] iex(4)> Enum.reverse(appended_four) [4, 3, 2, 1, 0]
  13. Maps • AKA Hashes iex(1)> my_hash = %{week: 7, fortnight:

    14, month: 30} %{fortnight: 14, month: 30, week: 7} iex(2)> my_hash[:month] 30 iex(3)> my_hash[:year] = 365 ** (CompileError) iex:3: cannot invoke remote function Access.get/2 inside match iex(4)> Map.put(my_hash, :year, 365) %{fortnight: 14, month: 30, week: 7, year: 365}
  14. Structs • Tied to a module • Sensible defaults •

    Like a named Map. defmodule Person do defstruct first: “”, last: “”, dob: “”, voted_for: “Mickey Mouse” def generate_person(first, last, dob, voted_for) do %Person{first: first, last: last, dob: dob, voted_for: voted_for} end end
  15. Anonymous Functions • First class • First-class citizens like lambdas

    and procs in Ruby iex(1)> double_func = fn x -> x * 2 end #Function<6.52032458/1 in :erl_eval.expr/1> iex(2)> double_func.(5) 10
  16. Others • Atoms ◦ names, like symbols in Ruby •

    Tuples • Dicts ◦ deprecated • Keywords ◦ list of tuples where the first element is an Atom, and the second is any value • Sets ◦ unique, similar to Ruby’s
  17. Pattern Matching • = (equal sign) is not used for

    assignment • It is a declaration that the left and right sides must be equal iex(1)> a = 1 1 iex(2)> a = 2 2 iex(3)> 2 = a 2 iex(4)> 3 = a ** (MatchError) no match of right hand side value: 2
  18. Pattern Matching • Can be used for matching a partial

    structure iex(1)> names = %{"alan" => "voss", "sam" => "sausage"} %{"alan" => "voss", "sam" => "sausage"} iex(2)> %{"alan" => _} = names %{"alan" => "voss", "sam" => "sausage"} iex(3)> %{"john" => _} = names ** (MatchError) no match of right hand side value: %{"alan" => "voss", "sam" => "sausage"}
  19. Pattern Matching • Can be used for drilling into a

    structure iex(1)> hobby_names = %{"bowling" => ["alan", "lance", "eric"], "pool" => ["colin", "tyler"]} %{"bowling" => ["alan", "lance", "eric"], "pool" => ["colin", "tyler"]} iex(2)> %{"pool" => [first, _]} = hobby_names %{"bowling" => ["alan", "lance", "eric"], "pool" => ["colin", "tyler"]} iex(3)> first "colin"
  20. Pattern Matching • Arbitrary nestings and complexity iex(1)> cards =

    %{"suits" => %{"hearts" => ["A", "K", "Q", "J", "T"], "spades" => ["9"}} %{"suits" => %{"hearts" => ["A", "K", "Q", "J", "T"], "spades" => ["9"]}} iex(2)> %{"suits" => %{"hearts" => ranks = [_, second, _, _, last]}} = cards %{"suits" => %{"hearts" => ["A", "K", "Q", "J", "T"], "spades" => ["9"]}} iex(3)> ranks ["A", "K", "Q", "J", "T"] iex(4)> last "T"
  21. Pattern Matching • Multiple function heads with different signatures •

    Starts at the first one, and if no pattern match, continues to the next • When a function matches the arguments, that is the one executed • The example below is absurd, but attempts to demonstrate: def add(0, x), do: x def add(x, 0), do: x def add(x, y), do: x + y
  22. Pattern Matching • Matching heads and tails (the rest) of

    a List iex(1)> list = [1,2,3] [1, 2, 3] iex(2)> [head | rest] = list [1, 2, 3] iex(3)> head 1 iex(4)> rest [2, 3]
  23. Guard Clauses • We still have multiple function heads •

    Now we want to change behavior based on a characteristic defmodule PrintStuff do def greeting(x) when is_integer(x), do: IO.puts “Hi, number #{x}” def greeting(x) when is_binary, do: IO.puts “Hello, string #{x}” def greeting(_), do: “Hello, but I’m unsure of what you are” end
  24. Guard Clauses • giving “odd” and “even” as a result

    defmodule PrintStuff do def type(x) when is_integer(x) and rem(x, 2) == 0, do: "even" def type(x) when is_integer(x), do: "odd" def type(x), do: "unknown" end
  25. Pipe • |> is the pipe declaration • Passes the

    previous result as the first argument to the next function • Ever find yourself doing this, in Ruby, for example? do_another_thing(do_something_else(do_something(x))) • How about this instead? x |> do_something |> do_something_else |> do_another_thing
  26. No Loops -> Recursion • We use recursion to accomplish

    things where we would use loops in Ruby • Tail recursion optimization (therefore, no stack memory problems) defmodule Count do def up_to(x), do: up_to(x, 0, []) |> Enum.reverse defp up_to(x, n, numbers) when n > x, do: numbers defp up_to(x, n, numbers), do: up_to(x, n + 1, [n | numbers]) end iex(1)> Count.up_to(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  27. Enumerations • Iterate over and manipulate items in collections iex(1)>

    numbers = [1,2,3,4] [1, 2, 3, 4] iex(2)> Enum.map(numbers, &(&1 * 2)) [2, 4, 6, 8] iex(3)> Enum.reduce(numbers, &(&1 + &2)) 10 iex(4)> Enum.drop(numbers, 2) [3, 4]
  28. Processes • Actors, in the Computer Science sense • Can

    be spawned • Can have messages sent to them with send • Can receive messages in return • receive is the only way to suspend a Process ◦ waits for a message ◦ A timeout can be specified, if desired • The core mechanism for maintaining state in Erlang and Elixir.