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

How Elixir is Transforming My Mind

How Elixir is Transforming My Mind

An introduction to how Elixir is changing the way I think about programming.

Nicholas Henry

November 17, 2015
Tweet

More Decks by Nicholas Henry

Other Decks in Programming

Transcript

  1. Functional Programming approaches the mainstream 1 » composability » modularity

    » equational reasoning » parallelism 1 https://www.quora.com/What-advantages-do-functional-programming-languages-introduce @nicholasjhenry, #myelixirstatus 2
  2. Becoming aware of these FP languages » Haskell » Scala

    » Elm » Clojure » and more... @nicholasjhenry, #myelixirstatus 3
  3. Meet Elixir » a screencast original published by PeepCode (now

    on Pluralsight) » presented by José Valim (created of Elixir) » parse the meta-data for a collection of video streaming files » and that was the first day a transformation began @nicholasjhenry, #myelixirstatus 11
  4. This is not an overview of Elixir (or an introduction

    to Functional Programming) @nicholasjhenry, #myelixirstatus 13
  5. These are the features that make Elixir fun for me

    (And I hope fun for you too) @nicholasjhenry, #myelixirstatus 14
  6. Five things you need to know 1. Elixir runs on

    the Erlang VM 2. Functional Programming Language 3. Created by José Valim 4. It's not "Ruby on the Erlang VM" 5. Yes, there is a web framework - Phoenix @nicholasjhenry, #myelixirstatus 15
  7. How Elixir has tranformed how I: 1. dispatch behaviour 2.

    work with data 3. think about concurrency @nicholasjhenry, #myelixirstatus 16
  8. #myelixirstatus I have: » not deployed a production application »

    read way to many books » attended ElixirConf this year in Austin, Texas @nicholasjhenry, #myelixirstatus 17
  9. how we dynamically choose behavior within our program -- Neal

    Ford, Functional Thinking @nicholasjhenry, #myelixirstatus 19
  10. Example: Refactoring from testing conditions to matching patterns Define a

    function that creates a new customer: * name cannot be blank. * state cannot be blank. * domain cannot be blank. * enabled must be true to start with. @nicholasjhenry, #myelixirstatus 22
  11. // original example if(name.isEmpty) { println("Name cannot be blank") null

    } else if(state.isEmpty) { println("State cannot be blank") null } else if(domain.isEmpty) { println("Domain cannot be blank") null } else { new Customer( 0, name, state, domain, true ) } @nicholasjhenry, #myelixirstatus 23
  12. # poorly translated example defmodule Customer do defstruct name: nil,

    state: nil, domain: nil, enabled: true # %Customer{name: name, state: state, domain: domain} end @nicholasjhenry, #myelixirstatus 25
  13. # poorly translated example defmodule Customer do defstruct name: nil,

    state: nil, domain: nil, enabled: true def create(name, state, domain) do end end @nicholasjhenry, #myelixirstatus 26
  14. # poorly translated example defmodule Customer do defstruct name: nil,

    state: nil, domain: nil, enabled: true def create(name, state, domain) do if name == "" do IO.puts "Name cannot be blank" else if state == "" do IO.puts "State cannot be blank" else if domain == "" do IO.puts "Domain cannot be blank" else %Customer{name: name, state: state, domain: domain} end end end end end @nicholasjhenry, #myelixirstatus 27
  15. Challenge » move from imperative implementation » we describing each

    step of the function » towards declarative implementation » we describe what we want @nicholasjhenry, #myelixirstatus 28
  16. Declarative Example » a declarative language we use every day:

    » results in more expressive programs SELECT * FROM CUSTOMERS; @nicholasjhenry, #myelixirstatus 29
  17. How? Pattern Matching » not regular expressions /hay/.match('haystack') #=> #<MatchData

    "hay"> » match scalar and non-scalar types » demonstrates why Elixir is not Ruby on the Erlang VM @nicholasjhenry, #myelixirstatus 30
  18. = is not assignment iex> a = 10 iex> 10

    = a irb> a = 10 => 10 irb> 10 = a SyntaxError: (irb):2: syntax error, unexpected '=', expecting end-of-input 10 = a ^ @nicholasjhenry, #myelixirstatus 31
  19. = is a match operator iex> a = 10 iex>

    20 = a ** (MatchError) no match of right hand side value: 10 » left-hand-side: "pattern" » right-hand-side: "term" @nicholasjhenry, #myelixirstatus 32
  20. Pattern-matching on Tuples iex> {a, b, c} = {:hello, "world",

    42} {:hello, "world", 42} @nicholasjhenry, #myelixirstatus 33
  21. Pattern-matching on Tuples iex> {a, b, c} = {:hello, "world",

    42} {:hello, "world", 42} iex> a :hello @nicholasjhenry, #myelixirstatus 34
  22. Pattern-matching on Tuples iex> {a, b, c} = {:hello, "world",

    42} {:hello, "world", 42} iex> a :hello iex> b "world" @nicholasjhenry, #myelixirstatus 35
  23. Pattern-matching on Tuples iex> {a, b, c} = {:hello, "world",

    42} {:hello, "world", 42} iex> a :hello iex> b "world" iex> c 42 @nicholasjhenry, #myelixirstatus 36
  24. Mis-match Pattern iex> {:hello, b, c} = {:hello, "world", 42}

    {:hello, "world", 42} @nicholasjhenry, #myelixirstatus 37
  25. Mis-match Pattern iex> {:hello, b, c} = {:hello, "world", 42}

    {:hello, "world", 42} iex> {:gidday, b, c} = {:hello, "world", 42} ** (MatchError) no match of right hand side value: {:hello, "world", 42} @nicholasjhenry, #myelixirstatus 38
  26. Pattern matching used everywhere » binding values to variables (just

    demonstrated) » conditions and loops (coming-up) » function declarations and invocation (coming-up) » recursion (not covered today) @nicholasjhenry, #myelixirstatus 39
  27. defmodule Customer do defstruct name: nil, state: nil, domain: nil,

    enabled: true def create(name, state, domain) do if name == "" do IO.puts "Name cannot be blank" else if state == "" do IO.puts "State cannot be blank" else if domain == "" do IO.puts "Domain cannot be blank" else %Customer{name: name, state: state, domain: domain} end end end end end @nicholasjhenry, #myelixirstatus 40
  28. # use pattern matching with a case statement defmodule Customer

    do defstruct name: nil, state: nil, domain: nil, enabled: true def create(name, state, domain) do case {name, state, domain} do # some pattern matching here soon end end end @nicholasjhenry, #myelixirstatus 41
  29. # use pattern matching with a case statement defmodule Customer

    do defstruct name: nil, state: nil, domain: nil, enabled: true def create(name, state, domain) do # case :term case {name, state, domain} do # clause (:head -> :body) {"", _state, _domain} -> IO.puts "Name cannot be blank" end end end @nicholasjhenry, #myelixirstatus 42
  30. # use pattern matching with a case statement defmodule Customer

    do defstruct name: nil, state: nil, domain: nil, enabled: true def create(name, state, domain) do case {name, state, domain} do {"", _state, _domain} -> IO.puts "Name cannot be blank" {_name, "", _domain} -> IO.puts "State cannot be blank" end end end @nicholasjhenry, #myelixirstatus 43
  31. # use pattern matching with a case statement defmodule Customer

    do defstruct name: nil, state: nil, domain: nil, enabled: true def create(name, state, domain) do case {name, state, domain} do {"", _state, _domain} -> IO.puts "Name cannot be blank" {_name, "", _domain} -> IO.puts "State cannot be blank" {_name, _state, ""} -> IO.puts "Domain cannot be blank" end end end @nicholasjhenry, #myelixirstatus 44
  32. # use pattern matching with a case statement defmodule Customer

    do defstruct name: nil, state: nil, domain: nil, enabled: true def create(name, state, domain) do case {name, state, domain} do {"", _state, _domain} -> IO.puts "Name cannot be blank" {_name, "", _domain} -> IO.puts "State cannot be blank" {_name, _state, ""} -> IO.puts "Domain cannot be blank" _ -> %Customer{name: name, state: state, domain: domain} end end end @nicholasjhenry, #myelixirstatus 45
  33. # use multi-clause functions defmodule Customer do defstruct name: nil,

    state: nil, domain: nil, enabled: true # multi-clause functions coming here soon end @nicholasjhenry, #myelixirstatus 46
  34. # use multi-clause functions defmodule Customer do defstruct name: nil,

    state: nil, domain: nil, enabled: true def create("", _state, _domain), do: IO.puts "Name cannot be blank" end @nicholasjhenry, #myelixirstatus 47
  35. # use multi-clause functions defmodule Customer do defstruct name: nil,

    state: nil, domain: nil, enabled: true def create("", _state, _domain), do: IO.puts "Name cannot be blank" def create(_name, "", _domain), do: IO.puts "State cannot be blank" def create(_name, _state, ""), do: IO.puts "Domain cannot be blank" end @nicholasjhenry, #myelixirstatus 48
  36. # use multi-clause functions defmodule Customer do defstruct name: nil,

    state: nil, domain: nil, enabled: true def create("", _state, _domain), do: IO.puts "Name cannot be blank" def create(_name, "", _domain), do: IO.puts "State cannot be blank" def create(_name, _state, ""), do: IO.puts "Domain cannot be blank" def create(name, state, domain), do: %Customer{name: name, state: state, domain: domain} end @nicholasjhenry, #myelixirstatus 49
  37. Dispatching Behaviour Pattern matching in Elixir (and FP languages in

    general) moves me away from an imperative approach, towards a more declarative approach, producing more expressive code. @nicholasjhenry, #myelixirstatus 51
  38. 2. How Elixir has transformed how I work with data

    @nicholasjhenry, #myelixirstatus 52
  39. Before Encapsulate data (state) within an object and mutate that

    data via method calls @nicholasjhenry, #myelixirstatus 53
  40. We transform data all the time » HTTP request to

    an HTTP Response » SQL query to collection of objects » a CSV document to hash @nicholasjhenry, #myelixirstatus 55
  41. Example: Count words iex> expected = %{ "one" => 1

    , "fish" => 4 , "two" => 1 , "red" => 1 , "blue" => 1 } iex> Words.count("one fish two fish red fish blue fish") == expected true @nicholasjhenry, #myelixirstatus 56
  42. Requirements 1. Count multiple occurances 2. Ignore punctuation 3. Include

    numbers 4. Normalize case @nicholasjhenry, #myelixirstatus 57
  43. Sketch sentence |> Remove punctuation |> Normalize case |> Split

    into words |> Count words @nicholasjhenry, #myelixirstatus 58
  44. Temporary variables module Words def count(sentence) do sentence1 = remove_punctuation(sentence)

    sentence2 = normalize_case(sentence1) sentence3 = split_into_words(sentence2) count_words(sentence3) end # private functions defined below end @nicholasjhenry, #myelixirstatus 59
  45. Staircasing module Words def count(sentence) do count_words( split_into_words( normalize_case( remove_punctuation(sentence)

    ) ) ) end # private functions defined below end @nicholasjhenry, #myelixirstatus 60
  46. Meet the pipe operator val |> my_function(a, b) same as:

    my_function(val, a, b) @nicholasjhenry, #myelixirstatus 62
  47. You have probably used pipes before ls | wc -l

    @nicholasjhenry, #myelixirstatus 63
  48. # refactored with the Pipe Operator module Words def count(sentence)

    do sentence end # private functions defined below end @nicholasjhenry, #myelixirstatus 64
  49. # refactored with the Pipe Operator module Words def count(sentence)

    do sentence |> remove_punctuation end # private functions defined below end @nicholasjhenry, #myelixirstatus 65
  50. # refactored with the Pipe Operator module Words def count(sentence)

    do sentence |> remove_punctuation |> normalize_case end # private functions defined below end @nicholasjhenry, #myelixirstatus 66
  51. # refactored with the Pipe Operator module Words def count(sentence)

    do sentence |> remove_punctuation |> normalize_case |> split_into_words end # private functions defined below end @nicholasjhenry, #myelixirstatus 67
  52. # refactored with the Pipe Operator module Words def count(sentence)

    do sentence |> remove_punctuation |> normalize_case |> split_into_words |> count_words end # private functions defined below end @nicholasjhenry, #myelixirstatus 68
  53. So, how has Elixir transformed how I work with data?

    @nicholasjhenry, #myelixirstatus 69
  54. Programming is transforming data, and the |> operator makes that

    transformation explicit. -- Dave Thomas, Programming with Elixir @nicholasjhenry, #myelixirstatus 70
  55. Concurrency vs. Parallelism 10 » Concurrency: dealing with multiple things

    at once » Parallelism: doing multiple things at once 10 http://concur.rspace.googlecode.com/hg/talk/concur.html#slide-5 @nicholasjhenry, #myelixirstatus 72
  56. Understanding the Actor Model 1. Actor == independent process 2.

    Shares nothing with any other process 3. processes send and receive messages @nicholasjhenry, #myelixirstatus 76
  57. Processes on the Erlang VM » not OS processes »

    extremely lightweight » 100K's processes » utilizes all CPU cores @nicholasjhenry, #myelixirstatus 77
  58. defmodule FakeSearchEngine do def search(query) do result = case query

    do # {atom, query_time, text} "Ruby" -> {:ok, 100, "Ruby is a dynamic, reflective, object-oriented..."} "Python" -> {:ok, 2000, "Python is a widely used general-purpose, high-level..."} "Elixir" -> {:ok, 4000, "Elixir is a functional, concurrent, general-purpose..."} _ -> {:error, 10000, "Results not found" } end result = {_, query_time, _} :timer.sleep query_time result end end @nicholasjhenry, #myelixirstatus 80
  59. What you need to know spawn(fn -> expression_1 #... expression_n

    end) @nicholasjhenry, #myelixirstatus 83
  60. iex> spawn(fn -> IO.inspect FakeSearchEngine.search("Ruby") end) #PID<0.100.0> {:ok, 100, "Ruby

    is a dynamic, reflective, object-oriented..."} @nicholasjhenry, #myelixirstatus 86
  61. Remember "each"? Ruby irb> a = [ "a", "b", "c"

    ] irb> a.each {|x| print "#{x} -- " } a -- b -- c -- => ["a", "b", "c"] Elixir iex> a = [ "a", "b", "c" ] iex> Enum.each(a, fn(x) -> IO.write "#{x} -- " end) a -- b -- c -- :ok @nicholasjhenry, #myelixirstatus 88
  62. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> IO.inspect FakeSearchEngine.search(query) ...>

    end) {:ok, 4000, "Elixir is a functional, concurrent, general-purpose..."} @nicholasjhenry, #myelixirstatus 90
  63. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> IO.inspect FakeSearchEngine.search(query) ...>

    end) {:ok, 4000, "Elixir is a functional, concurrent, general-purpose..."} {:error, 10000, "Results not found"} @nicholasjhenry, #myelixirstatus 91
  64. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> IO.inspect FakeSearchEngine.search(query) ...>

    end) {:ok, 4000, "Elixir is a functional, concurrent, general-purpose..."} {:error, 10000, "Results not found"} {:ok, 100, "Ruby is a dynamic, reflective, object-oriented..."} :ok @nicholasjhenry, #myelixirstatus 92
  65. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> spawn(fn -> IO.inspect

    FakeSearchEngine.search(query) end) ...> end) @nicholasjhenry, #myelixirstatus 94
  66. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> spawn(fn -> IO.inspect

    FakeSearchEngine.search(query) end) ...> end) :ok @nicholasjhenry, #myelixirstatus 95
  67. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> spawn(fn -> IO.inspect

    FakeSearchEngine.search(query) end) ...> end) :ok {:ok, 100, "Ruby is a dynamic, reflective, object-oriented..."} @nicholasjhenry, #myelixirstatus 96
  68. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> spawn(fn -> IO.inspect

    FakeSearchEngine.search(query) end) ...> end) :ok {:ok, 100, "Ruby is a dynamic, reflective, object-oriented..."} {:ok, 4000, "Elixir is a functional, concurrent, general-purpose..."} @nicholasjhenry, #myelixirstatus 97
  69. iex> Enum.each(["Elixir", "Java", "Ruby"], fn(query) -> ...> spawn(fn -> IO.inspect

    FakeSearchEngine.search(query) end) ...> end) :ok {:ok, 100, "Ruby is a dynamic, reflective, object-oriented..."} {:ok, 4000, "Elixir is a functional, concurrent, general-purpose..."} {:error, 10000, "Results not found"} @nicholasjhenry, #myelixirstatus 98
  70. So, how has Elixir transformed how I think about concurrency?

    Now I spawn processes with the same ease and frequency as I instantiate objects in Ruby. @nicholasjhenry, #myelixirstatus 99
  71. Begin your own transformation 1. Watch "Think Different(ly)" by Dave

    Thomas 3 2. Work through the Elixir Guides 4 3. Start practicing on Exercism.io 5 5 http://exercism.io/ 4 http://elixir-lang.org/getting-started 3 http://bit.ly/think-diff @nicholasjhenry, #myelixirstatus 100
  72. Thank you! » Nicholas Henry » independent Ruby/Rails developer »

    @nicholasjhenry » http://blog.firsthand.ca » references will be posted along with a recording and slides @nicholasjhenry, #myelixirstatus 101