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

Intro to Elixir

Intro to Elixir

from my talk at pdx-erlang in April 2014. Introducing Elixir from the perspective of someone who works primarily in a scripting language such as Ruby, Javascript or Python.


Matthew Lyon

April 17, 2014


  1. Intro to Elixir from a 
 scripting language perspective

  2. Matthew Lyon @mattly Ruby, Javascript/ CoffeeScript, Bash, Python, SQL, Max/MSP

  3. What can this help me build? 2004, Rails: Web +

    SQL 2006, jQuery: sane frontend 2010, node: concurrent…?
  4. What can this help me build? easily scalable software that

    doesn’t wake me up at 3am
  5. erlang is awesome

  6. erlang is awesome the VM

  7. concurrency model lightweight processes actor pattern

  8. OTP: “killer app” design principles library used to build systems

    w/ 99.9999999% uptime
  9. syntax is… weird has a lot of gotchas {foo, State}:bar(1,2)

    foo:bar(1,2,{foo, State}) →
  10. stdlib interfaces many inconsistencies ! lists:map(Fun, List) -> List2 string:substr(Str,

    Start, Length) -> SubString !
  11. Ralph Waldo Emerson “Consistency is the hobgoblin of little minds.”

  12. “Inconsistency is the hobgoblin of unmaintainable software.”

  13. goto fail heartbleed !

  14. Elixir created by rails-core 
 member Jose Valim

  15. kinda like coffeescript but not really

  16. plain syntax is 
 easier to read and reduces gotchas

  17. thing, arguments ! Enum.map(list, fun) -> list2 String.slice(str, start, len)

    -> substr stdlib interfaces 
 are consistent
  18. metaprogramming there when you want it, transparent when you don’t

  19. it has amazing tooling
 great build system
 doctests dependency

    management included testing library
  20. it’s erlang semantics ! not ‘ruby compiling to erlang’ “CoffeeScript

    is just Javascript”
  21. it compiles to erlang bytecode, integrates w/ existing ecosystem !

    though you’ll have to learn a bit of erlang too ! similar to Clojure, Scala
  22. unlearn you 
 some OOP 
 for great good

  23. object-oriented is not the only good way to design software

  24. functional programming doesn’t have to be mathematical or complex

  25. assignment & control flow are not essential to programming

  26. locks & semaphores 
 are not essential to concurrency

  27. functional programming 
 treats computation as the evaluation of mathematical

    functions and avoids state and mutable data. Functional programming emphasizes functions that produce results that depend only on blah blah expressions blah blah blah declarative blah blah blah no side effects blah blah blah lambda calculus blah blah blah monads blah blah blah category theory source: wikipedia
  28. functions transform

  29. functions are small, focused grep '/home' /etc/passwd

  30. functions are composable grep '/home' /etc/passwd | wc -l

  31. functions are repeatable grep '/home' /etc/passwd | wc -l 23

  32. functions don’t have side-effects a = "hello" a.capitalize! # a

    = “Hello" ! a = String.capitalize(a)
  33. functions don’t mutate state a = [3,2,1] a.reverse() // modifies

    a a.concat([4,5]) // new array ! a = Enum.reverse(a) a = Enum.concat(a, [4,5])
  34. “How do we program without GOTO?” Computer Science, 1968

  35. “How do we program without mutable state?” Computer Science, 2014

  36. immutability 
 allows re-using memory

  37. immutability 
 is the key to concurrency

  38. that’s great… ! so, uh, how do I 

  39. Classes Instances behavior state

  40. Classes Instances behavior state Modules Processes

  41. Classes Instances behavior state Modules Processes you mean like, threads?

  42. processes are light elixir --erl "+P 1000000" \ -r chain.exs

    \ -e "Chain.run(400_000)" {5325361, "Result is 400000"} that’s 400k processes 
 spawning sequentially, running simultaneously in 5.3 seconds on a MacBook Air
  43. actors, not objects

  44. actors send & receive messages

  45. upon receiving a message, an actor may send a finite

    number of messages to other actors
  46. upon receiving a message, an actor may create a finite

    number of other new actors

  47. upon receiving a message, an actor may designate the behavior

    to be used for the 
 next message
  48. but really 
 you don’t have to 
 think too

    much about it, thanks to OTP
  49. first, how to write sequential code
 all over again

  50. know your type(s)

  51. base types Integers, 
 arbitrary precision Floats,
 IEEE 754 64-bit

    Atoms, aka symbols Tuples, not arrays Lists, not arrays Binaries, not strings Functions Pids, Ports, References
  52. atoms :crypto the name is the value ! true, false,

    nil are atoms
  53. tuples {:arbitrary, items, [1,2]} any element accessible in constant time

  54. lists [:arbitrary, items, {1,2}] [:arbitrary|[items|[{1,2}]]] [head | tail] head accessible

    in constant time think of them like stacks
  55. implemented types Strings, 
 using binaries or 
 “character lists”

 using tuples Maps, aka dicts / 
 hashes / POJOs, 
 using Lists Streams, lazy-
 loaded enums Regexs, Ranges, Sets
  56. ranges Enum.map(1..5, fn(i)-> i end) ! start, end can be

    any type enumeration requires integers
  57. records defrecord RgbColor, red: 0, 
 blue: 0, green: 0

    ! like loosely typed Structs
  58. records RgbColor[red: 0.5, 
 blue: 100, 
 green: 'a7'] !

    ! ! …really loose
  59. records new_color =
 color.red(42) ! ! the only non-module type

    with functions
  60. functions adder = fn(a) -> fn (b) -> a +

    b end end can pass as values
  61. unnamed functions add2 = adder.(2) add2.(3) 5 remember original environment

  62. inline functions Enum.map([1,2], 
 &(&1 * &1)) save needless fn

  63. composable inlines divrem = &{div(&1,&2), rem(&1,&2)} divrem.(13,5) # {2,3} lexer

    is pretty smart
  64. detachable len = &Enum.count/1 ! great for composing functions

  65. named functions defmodule Geometry do def area(item) do # math

    goes here end end
  66. many small functions ! area doesn’t have to branch by

    shape type
  67. pattern matching 
 assert, don’t assign

  68. LHS = RHS

  69. foo = 1 1 = foo ! these are assertions

  70. foo = 1 1 = foo 2 = foo **

    (MatchError) no match of right hand side value: 1
  71. foo = 1 1 = foo ! identifiers on LHS

    are bound
  72. foo = 1 1 = foo ^foo = 2 **

    (MatchError) no match of right hand side value: 2
  73. list = [1,4,9,16] [1,a|rest] = list 4 = a RHS

    compounds are destructured
  74. list = [1,4,9,16] [2,a|rest] = list ** (MatchError) no match

 of right hand side value: [1,4,6,9]
  75. {Color, :rgb, {r,g,b}} = my_color ! destructuring can be used

    with any depth
  76. ! {n,_} = Float.parse("1.618") ! ignore unwanted values 

  77. functions, reloaded

  78. ! defmodule Color do def hex(:rgb, vals={r,g,b}) do # no

    conversion needed function signatures 
 perform pattern matching
  79. defmodule Factorial do def of(0), do: 1 def of(x), do:

    x * of(x-1) end declare multiple signatures instead of using if or case
  80. defmodule Factorial do def of(0), do: 1 def of(x) when

    is_number(x) and x > 0, do: x * of(x-1) end guard clauses 
 augment pattern matching
  81. Factorial.of(-100) ** (FunctionClauseError) no function clause matching in Factorial.of/1 guard

 are limited, but very useful
  82. defmodule Factorial do def of(x) when is_number(x) and x >

    0, do: of(x, 1) defp of(0,a), do: a defp of(x,a), do: of(x-1, a*x) end tail-call optimization 
 doesn’t add to the stack
  83. ! defmodule Color do def rgb(r\\0, g\\0, b\\0) do default

 match left-to-right save a ton of boilerplate
  84. home = &(Regex.match?(~{/home},&1)) ! blob = File.read!('/etc/passwd') lines = String.split(blob,

    "\n") matches = Enum.filter(lines, home) initial = Enum.take(matches, 5) users = Enum.map(initial, get_user) without mutation… lots of asserting?
  85. Enum.map( Enum.take( Enum.filter( String.split( File.read!( '/etc/passwd'), "\n"), home), 5), get_user)

    no… just… no
  86. '/etc/passwd' |> File.read! |> String.split("\n") |> Enum.filter(home) |> Enum.take(5) |>

    Enum.map(get_user) pipeline operator result of expression on left is first param for fn on right
  87. functions are composable echo '/etc/passwd' | xargs -n 1 cat

    | grep '/home' | head -n 5 | cut -d: -f 1
  88. it’s easier to reason about small functions with no internal

  89. imagine there’s 
 no if statement ! you couldn’t if

    you tried
  90. erlang doesn’t have if, but elixir does. it’s a macro.

  91. ! if x < 3, do: IO.puts "hello", else: IO.puts

    "bye" and that’s how you use a macro
 there’s also an unless macro
  92. but please don’t use else with unless

  93. if else? meet cond

  94. result = cond do whole(val,3) and whole(val,5) -> "FizzBuzz" whole(val,

    3) -> "Fizz" whole(val, 5) -> "Buzz" true -> val end you probably just want function calls instead
  95. case or switch? we’ve got case ! hint: it uses

    pattern matching
  96. ! case thing do c = Color[type=:rgb, {r,g,b}] when is_number(r)

    and r < 1 -> pattern matching with guards & destructuring
  97. unmatched cond clauses or case patterns will raise

  98. exceptions? ! they’re for when something unanticipated happens

  99. ! case File.open("chain.exs") do { :ok, file } -> #

    something { :error, reason } -> # uh oh end this code anticipates a problem with file reading
  100. file = File.open!("chain.exs") this code doesn’t ! elixir convention:

    functions raise on error
  101. let it crash ! don’t make ad-hoc decisions when something

    goes wrong
  102. do I actually know how I’m supposed to handle an

  103. concurrency through smart sequences

  104. actors are processes that talk and listen

  105. Classes Instances behavior state Modules Processes processes are cheap

  106. my god, 
 it’s full of processes 2001 by Stanley

  107. ! pid = spawn(ModuleName,
 [arg1, arg2]) spawning new processes

    is easy
  108. ! send(pid, [val1, val2]) sending messages is 
 fire &

  109. def function_name(arg1, arg2) do receive do :hello -> IO.puts("hello") val

    -> do_something(val) end end receiving messages is straightforward and uses pattern matching
  110. send(pid, {self, val1}) receive do response -> # handle !

    def function_name(arg1, arg2) do receive do {p,v} -> send(p, process(v)) combine these 
 to wait for a response
  111. each process 
 has its own mailbox ! receive reads

    the first message
  112. def function_name(state) do receive do {p,v} -> send(p, process(v)) end

    function_name(new_state) end an actor 
 may designate the behavior 
 for the next message
  113. tail-call optimization means we aren’t 
 adding to the stack

  114. combine sending and receiving to parallelize

  115. Parallel.map( 1..n, &Factorial.of/1) ! ! elixir -r par.exs -e “Parallel.fac(5000)”

    5000 items, last is 422… took 13547672 microseconds
  116. that was Factorial.of(n) for n in 1..5000 running simultaneously on

    all cores
  117. beam lets you connect multiple VMs ! (beyond our scope)

  118. the supervisor pattern is for managing processes

  119. defmodule GhostWhisper do def talk_to_dead do exit(99) # impossible something

    went wrong? crash
  120. {pid,ref} = Process.spawn_monitor( &GhostWhisper.talk_to_dead/0) receive do {:DOWN,^ref,:process,^pid,r} -> IO.puts(“#{inspect(pid)},#{r}”) monitoring

    tells you when
 a process dies
  121. def dead_waiter do sleep 500; send(pid, "hi") ! spawn_link(GW,:dead_waiter,[self()]) receive

    do msg -> # never gets here after 100 -> exit(99) linking sets up process
  122. extending with metaprogramming & monkeypatching ! (don’t worry, no actual

  123. macros are metaprogramming done right

  124. homoiconicity the language has direct access to its own internal

  125. hygenic macros won’t clobber each others’ variables or those of

    the functions that use them ! you can turn hygiene off
  126. low-overhead macros modify the AST at compile time

  127. defmacro if(condition, body) do yes = Keyword.get(body, :do, nil) no

    = Keyword.get(body, :else, nil) quote do case unquote(condition) do false -> unquote(no) _ -> unquote(yes) quote writes to the output tree unquote writes variable content
  128. defmacro truthy(condition, body) do yes = Keyword.get(body, :do, nil) no

    = Keyword.get(body, :else, nil) quote do case unquote(condition) do val when val in [false, nil, 0, "", [], {}] -> unquote(no) _ -> unquote(yes) like javascript’s falsiness? here you go
  129. defmacro dynamic_def(name) do quote bind_quoted: [name: name] do def unquote(name)()

    do unquote(name) ! [:red, :green] |> Enum.each(&Unholy.dynamic_def(&1)) want to dynamically define 
 methods at compile time? 
 use a binding
  130. protocols are polymorphism for functions

  131. think of them as 
 type-specific decorators

  132. defimpl Enumerable, for: Bag do def count(collection) do # returns

    number of items in bag def member?(collection, value) # {:ok, boolean} def reduce(collection, acc, fun) # apply fun to the collection enables Enum to use Bag values
  133. implemented types like Sets, Streams, Ranges use the Enumerable protocol

  134. defimpl Access, for: Bag def access(container, key) # gets key

    from container ! my_bag[:thing] enables Bag variables 
 to use [] accessors
  135. Inspect is a protocol, if you want to pretty- print

    your own types
  136. behaviours are interface definitions for modules

  137. erlang has a british- english influence, so get used to

    the brit spelling
  138. defmodule My.Server do use GenServer.Behaviour @version 3 def init(initial) ::

    result def handle_call(request, from, state) 
 :: result def handle_cast(request, state) 
 :: result def handle_info(info, state) :: result def terminate(reason, state) def code_change(oldVsn, state, extra)
 :: status OTP server interface definition
  139. declaring behaviour use and implelenting an interface lets you leverage

    powerful abstractions
  140. great tooling 
 makes a 
 foreign environment easier

  141. mix can throw down against rake, grunt, easy_install, npm, make,

    pip, & bundler
  142. create projects, 
 manage deps, test, & compile, generate docs

  143. documenation isn’t merely grafted on

  144. defmodule MyModule do @moduledoc """ ## This is my module

    “"" ! iex(1)> h(MyModule) MyModule ## This is my module
  145. @doc """ ignores its arg and returns 1 """ def

    function(_), do: 1 ! iex(2)> h(MyModule.function/1) def function(_) ignores its arg and returns 1
  146. # mix.exs defp deps do [ { :ex_doc, github:'elixir-lang/ex_doc'} ]

    end ! $ mix docs ExDoc makes HTML docs for you
  147. ExUnit leverages macros for the power of awesome

  148. defmodule MathTest do use ExUnit.Case ! test "math is correct"

    do assert( 2 + 2 = 5 ) end end test and assert are macros
  149. $ mix test Compiled lib/test/supervisor.ex Compiled lib/test.ex Generated test.app 1)

    test math is correct (MathTest) ** (ExUnit.ExpectationError) expected: 5 to match pattern (=): 2 + 2 at test/math_test.exs:5 assertion types are implied 
 by pattern matching & syntax tree
  150. the OTP sales pitch ! (sadly beyond our scope)

  151. libraries implementing best-practice patterns

  152. write your servers, state machines, event handlers & supervisors 

    to a common 
 abstract interface
  153. def current_state_name(:event_name, details) do # transition logic { :next_state, :new_state_name,

    details } end ! def new_state_name(:event_name, …) the state machine DSL is sexy
  154. leverage battle-tested patterns, tools for debugging, release management & more

  155. it’s still a young community, 
 but building on old

  156. None
  157. elixir-lang.org elixirsips.com Programming Elixir by Dave Thomas Elixir in Action

    by Saša Jurić
  158. erlang.org Programming Erlang by Joe Armstrong Learn You Some Erlang

    For Great Good by Frank Hébert learnyousomeerlang.com our meetup hosts, erlangsolutions.com
  159. thank you! ! Matthew Lyon twitter / github: mattly speakerdeck.com/mattly/intro-to-elixir

    ! typefaces: Source Sans Pro & Source Code Pro and ChunkFive