$30 off During Our Annual Pro Sale. View Details »

Foray into Functional Programming with Elixir

Trevor Brown
November 26, 2014

Foray into Functional Programming with Elixir

Functional programming languages provide many advantages over traditional OO languages. Functional languages make data manipulation (e.g. parsing binary data) easier. Their lack of mutable state (all data is immutable) also makes functional applications simpler than their OO counterparts. Learning how to solve problems and write software in functional languages like Elixir can be difficult at first. Writing real-world applications in functional languages requires a different approach to problem solving. In this talk I cover core concepts specific to functional programming required to start writing simple Elixir applications quickly. I will also share some things I have discovered over the past year that have helped me make the transition from OO to functional programming. Focusing on data and data manipulations is key to being productive in a functional language.

Trevor Brown

November 26, 2014
Tweet

More Decks by Trevor Brown

Other Decks in Programming

Transcript

  1. Foray into Functional
    Programming with Elixir
    Trevor Brown

    View Slide

  2. Trevor Brown
    Sarasota, Fl

    Ruby, JavaScript,
    Erlang, and Elixir

    Erlang developer at Voalte

    View Slide

  3. Elixir?
    •  Built on top of the Erlang VM
    •  Compiles down to BEAM bytecode
    •  Helps to have an understanding of 

    Erlang and the underlying VM

    View Slide

  4. What is Erlang?
    •  Designed for scalability

    and real-time systems
    •  Functional
    •  Simplifies concurrent 

    programming
    •  Fault-Tolerant (“let it crash” error handling
    philosophy)
    •  Bytecode runs on the Erlang VM
    •  Designed by Joe Armstrong in 1986
    •  Legendary nine 9’s of uptime

    View Slide

  5. What is Elixir?

    View Slide

  6. Elixir
    •  Dynamic
    •  Functional
    •  Built on top of the Erlang VM
    •  Leverages the VM to build concurrent,
    distributed and fault-tolerant applications.
    •  Created in 2011 by José Valim
    •  1.0.0 was released in September

    View Slide

  7. Myth
    “Elixir is like Ruby on the Erlang VM”


    •  Elixir is VERY different from Ruby, but the syntax
    is similar
    –  Functional vs OO
    –  Pattern Matching
    –  Recursion
    –  Immutable Data Structures

    View Slide

  8. Myth
    “Elixir is just CoffeeScript for Erlang”

    •  Elixir provides better tooling, first-class docs,
    and easier metaprogramming than Erlang
    •  Additional data types

    View Slide

  9. Why use Elixir?
    •  All the benefits of Erlang
    •  Easily reuse Erlang libraries
    •  No additional performance costs
    •  Better tooling, which allows for
    greater productivity
    •  Better syntax
    •  Simplified metaprogramming

    View Slide

  10. Elixir Basics

    View Slide

  11. Data Types
    •  All data is immutable

    •  Atoms are just like symbols in Ruby
    •  Booleans have been faked using the atoms
    ‘true’ and ‘false’
    •  Strings
    :hello_world  
    :a  
    true  #  booleans  are  also  atoms  
    Atoms

    View Slide

  12. Binaries and Numbers
    #  Binaries  
    <<97,98,99>>  
    <<"abc">>  
       
    #  Integers  
    12  
       
    #  Floats  
    23.5  

    View Slide

  13. Tuples
    •  Ordered collection of elements with a fixed size.
    •  No array type, only linked lists.
    •  Each item in the list contains the value, along
    with a pointer to the next item in the list.
    #  Tuple  
    {:one,  :two,  :three}  
       
    #  Tuples  that  are  stored  contiguously  in  memory  
    {1,2,3,4}  
       
    #  We  can  access  a  tuple  element  with  the  `elem`  
    function  
    elem({1,  2,  3},  0)  #=>  1  

    View Slide

  14. Lists
    •  No array type, only linked lists.
    •  Each item in the list contains the value, along
    with a pointer to the next item in the list.
    #  List  
    [1,  2,  3]  
       
    letters  =  [:a,  :b,  :c]  
       
    #  Items  can  be  added  to  front  of  the  list  easily  
    [:d|letters]  #=>  [:d,  :a,  :b,  :c]  

    View Slide

  15. String(ish) Types
    •  No true string type
    •  Elixir represents strings as binaries underneath
    •  Lists can also be used to represent string data
    "José"  #=>  "José"  -­‐  UTF-­‐8  binary  string  
    'char  list'  #=>  'char  list'  -­‐  char  list  
       
    #  `?a`  returns  the  ASCII  integer  for  the  letter  `a`  
    ?a  #=>  97  
       
    <>  #=>  "abc"  
    [?a,  ?b,  ?c]      #=>  'abc'  

    View Slide

  16. #  Maps  
    %{  one:  1,  two:  2,  three:  3  }  
       
    #  HashDicts  
    [one:  1,  two:  2,  three:  3]  
       
    #  Keyword  Lists  allow  duplicate  values  
    [one:  1,  one:  "One",  two:  2]  
       
    #  Structs  
    defmodule  User  do  
       defstruct  username:  "",  email:  "",  real_name:  ""  
    end  
    More Data Types…
    •  Maps, HashDicts, Keyword Lists, and Structs

    View Slide

  17. Modules
    •  Code organization is done via modules and
    functions.
    defmodule  Ping  do  
       @moduledoc  """  
       Sample  module  
       """  
       
       #  Add  functions  here  
    end  
     

    View Slide

  18. Functions
    •  In Elixir functions are identified by name and
    arity.
    #  add/2  has  two  clauses  
    def  add(0,  0)  do:  0    
    def  add(x,  y)  do:  x  +  y  
       
    #  add/3  has  one  clause    
    def  add(x,  y,  z)  do    
       x  +  y  +  z  
    end  
     

    View Slide

  19. Pattern Matching

    View Slide

  20. Pattern Matching
    •  Pattern Matching is used extensively in Elixir
    •  The equal signs isn’t assignment, it’s a
    challenge to make both sides equal
    :a  =  :a  #=>  :a  
    :a  =  :b  #=>  **  (MatchError)  no  match  of  right  
    hand  side  value:  :b  
     
    letter  =  :a  #=>  :a  
    letter  #=>  :a  

    View Slide

  21. Matching Tuples
    •  Tuples can be used in pattern matching
    {:ok,  foo}  =  {:ok,  "foo  bar  baz"}  
    foo  #=>  “foo  bar  baz”  
       
    {:ok,  foo}  =  {:error,  :crashed}  #=>  **  
    (MatchError)  no  match  of  right  hand  side  value:  
    {:error,  :crashed}  
     

    View Slide

  22. Matching Lists
    •  Lists can be used in pattern matching
    [head|tail]  =  [1,2,3]  
    head  #=>  1  
    tail  #=>  [2,3]  
     
    [first,second,third]  =  [1,2,3]  
    second  #=>  2  
     
    [one,two]  =  [1,2,3]  #=>  **  (MatchError)  no  match  of  
    right  hand  side  value:  [1,  2,  3]  
     
    [first,second|rest]  =  [1,2,3]  
    second  #=>  2  

    View Slide

  23. Variables in Patterns
    •  Variables cannot be re-bound to new values
    during a match
    •  If a variable name appears twice in a pattern the
    values it is matched against must be equal
    {num,  num}  =  {1,  1}  #=>  {1,  1}  
    {num,  num}  =  {1,  2}  #=>  **  (MatchError)  no  match  
    of  right  hand  side  value:  {1,  2}  
     

    View Slide

  24. Underscore Variable
    •  Can be used in place of a normal variable in a
    pattern
    •  No values are ever bound to it, meaning it can
    match any value anytime it is used.
    •  It's common to assign a value to `_` if we don't
    need it
    {_,  _}  =  {1,  1}  #=>  {1,  1}  
    {_,  _}  =  {1,  2}  #=>  {1,  2}  
    {num,  num}  =  {1,  2}  #=>  **  (MatchError)  no  match  
    of  right  hand  side  value:  {1,  2}  

    View Slide

  25. The ^ Variable Prefix
    •  Variables prefixed with the caret force patterns
    to match against the value previously bound to
    the variable
    username  =  "José"  
       
    {:name,  ^username}  =  {:name,  "Joe"}  
    #=>  **  (MatchError)  no  match  of  right  hand  side  
    value:  {:name,  "Joe"}  
       
    {:name,  ^username}  =  {:name,  "José"}  
    #=>  {:name,  "José"}  

    View Slide

  26. Pattern Matching
    •  Functions parameters are patterns
    #  These  are  not  parameters,  they  are  patterns  
    #  Evaluated  if  value  passed  in  matches  `0`  
    def  fib(0)  do  0  end  
     
    #  Evaluated  if  value  passed  in  matches  `1`  
    def  fib(1)  do  1  end  
     
    #  Evaluated  if  the  first  two  clauses  don't  match  
    def  fib(n)  do  fib(n-­‐1)  +  fib(n-­‐2)  end  
     

    View Slide

  27. Pattern Matching
    •  Case statements also use pattern matching
    case  do_something(work)  do  
       {:ok,  result}  -­‐>  #  success  
           do_something_with_result(result)  
       {:invalid_work,  error}  -­‐>  #  special  case  
           handle_invalid_work(error)  
       error  -­‐>  #  error  
           handle_error(error)  
    end  

    View Slide

  28. First Class Functions

    View Slide

  29. First Class Functions
    •  All functions in Elixir are first class
    •  There are two types of functions:
    –  Named – does not inherit scope
    –  Anonymous – inherits the scope of wherever it was
    defined functions
    •  Both can be referenced and passed around

    View Slide

  30. Named Functions
    defmodule  Hello  do  
       def  greet("SunJUG")  do  "hello  everyone!"  end  
       def  greet(name)  do  "hello  "  <>  name  end  
    end  
       
    hello  =  &Hello.greet/1  #=>  
    #Function<6.90072148/1  in  :erl_eval.expr/5>  
     
    hello.("SunJUG")  #=>  "hello  everyone!”  
     
    Hello.greet("José")  #=>  "hello  José  

    View Slide

  31. Anonymous Functions
    special_greeting  =  "hello  everyone!”  
     
    #  Inherits  scope  creating  closure  
    greet  =  fn  
       "SunJUG"  -­‐>  special_greeting    
       name  -­‐>  "hello  "  <>  name  
    end  
       
    greet  #=>  #Function<6.90072148/1  
    in  :erl_eval.expr/5>  
     
    greet.("SunJUG")  

    View Slide

  32. Passing Functions
    user1  =  [first_name:  "Joe",  last_name:  "Armstrong"]  
    user2  =  [first_name:  "José",  last_name:  "Valim"]  
    users  =  [user1,  user2]  
       
    #  Function  that  gets  the  :first_name  from  a  keyword  
    list  
    first_name  =  fn(user)  -­‐>  
       Keyword.fetch!(user,  :first_name)  
    end  
       
    #  Map  the  first_name  function  to  every  item  in  the  
    users  list  
    Enum.map(users,  first_name)  #=>  ["Joe",  "José"]  

    View Slide

  33. Passing Functions
    user1  =  [first_name:  "Joe",  last_name:  "Armstrong"…]  
    user2  =  [first_name:  "José",  last_name:  "Valim"…]  
    users  =  [user1,  user2]  
       
    #  Function  that  returns  a  function  that  fetches  the  
    `field`  key  value  from  a  keyword  list  
    get_field  =  fn(field)  -­‐>  
       fn(user)  -­‐>  
           Keyword.fetch!(user,  field)  
       end  
    end  
       
    Enum.map(users,  get_field.(:last_name))  #=>  
    ["Armstrong",  "Valim"]  

    View Slide

  34. Recursion

    View Slide

  35. •  Recursive functions call themselves
    •  Recursion functions are used when repetition is
    needed
    •  If a recursive function always calls itself and
    never returns an infinite loop will be created
    Recursion

    View Slide

  36. •  Here we recursively calculate the sum of a list
    •  Note the empty list clause which ends recursion
    Recursion Step by Step
    User  invokes  
    sum_list([1,2,3,4],  0)  
    sum_list([2,3,4],  0+1)  
    sum_list([3,4],  2+1)  
    sum_list([4],  3+3)  
    sum_list([],  4+6)  
    10  
    def  sum_list([head  |  tail],  acc)  do  
       sum_list(tail,  acc  +  head)  
    end  
       
    def  sum_list([],  acc)  do  
       acc  
    End  

    View Slide

  37. Another Example
    def  factorial(0)  do  
       1  
    end  
       
    def  factorial(number)  do  
       factorial(number  -­‐  1)  *  number  
    end  
    •  A recursive function that computes factorials

    View Slide

  38. Another Example
    def  factorial(0)  do  
       1  
    end  
       
    def  factorial(number)  do  
       factoral(number  -­‐  1)  *  number  
    end  
    •  A recursive function that computes factorials
    User  invokes  factorial(5)  
    factorial(5-­‐1)  *  5  
    factorial(4-­‐1)  *  4  *  5  
    factorial(3-­‐1)  *  3  *  4  *  5  
    factorial(2-­‐1)  2  *  3  *  4  *  5  
    factorial(1-­‐1)  *  1  *  2  *  3  *  4  *  5  
    1  *  1  *  2  *  3  *  4  *  5  
    60  

    View Slide

  39. Tail Call Optimization
    •  If the last expression is solely another function
    call the function is tail call optimized
    •  This means:
    –  the no new stack level is created
    –  function is simply re-executed with the new
    arguments
    •  Not all functions that call themselves on the last
    line are tail call optimized
    •  If a function is not tail call optimized it will run
    without errors until the stack uses all the
    memory

    View Slide

  40. Tail Call Optimization
    def  factorial(0,  result)  do  
       result  
    end  
       
    def  factorial(number,  result)  do  
       factorial(number  -­‐  1,  result  *  number)  
    end  
    def  factorial(0)  do  
       1  
    end  
       
    def  factorial(number)  do  
       factorial(number  -­‐  1)  *  number  
    end  
    Optimized
    Not Optimized

    View Slide

  41. Concurrency

    View Slide

  42. The Erlang VM
    •  Each instance of the Erlang VM is called a node
    •  You can have one or more nodes on a physical
    machine
    •  You can spread nodes across multiple physical
    machines
    •  Nodes are one OS process, with one or more
    threads

    View Slide

  43. The Erlang VM
    CPU   CPU   CPU   CPU  
    Thread   Thread   Thread   Thread  
    Virtual  Machine  Process  
    Process  
    Process   Process   Process  
    Process   Process  

    View Slide

  44. Processes
    •  Erlang processes are not OS processes
    •  Erlang VM distributes work of Erlang processes
    between the OS threads of the VM’s OS process
    •  Processes are concurrent
    •  There is no “main” process. Everything is a
    process and there are not special types of
    processes

    View Slide

  45. Processes
    •  Processes are independent of each other
    •  Processes cannot modify each other
    •  No data is shared between processes
    •  Processes communicate via message passing
    •  Processes are extremely lightweight
    •  Processes can be linked together, processes
    can also be monitored by other processes

    View Slide

  46. Message Passing

    View Slide

  47. Processes are Actors
    •  Implements the actor model
    •  Processes encapsulate state
    •  Elixir’s processes are more object-oriented
    than objects in object-oriented languages

    View Slide

  48. Processes
    •  Message being sent
    to a process
    •  A process receiving
    messages
    #  `pid`  is  a  process  
    message  =  {:add,  1,  2}  
    send  pid,  message  
    #  Listen  for  messages  
    receive  do  
       {:add,  x,  y}  -­‐>  
           x  +  y  
       {:subtract,  x,  y}  -­‐>  
           x  -­‐  y  
       _  -­‐>  
           :error  
    end  

    View Slide

  49. PingPong
    •  Two processes sending messages back and
    forth
    •  Resulted in an infinite loop
    <0.83.0>  
    Ping
    <0.97.0>  
    Pong
    {:ping,  <0.83.0>}  
    {:pong,  <0.97.0>}  
    <0.59.0>  
    Shell Process (Us)
    {:ping,  <0.83.0>}  
    spawn_link  
    Receive  blocks  

    View Slide

  50. ProcessChain
    •  Thousands of processes arranged in a loop
    passing hundreds of messages around the loop.
    •  Demonstrates the efficiency of the Erlang VM’s
    message passing and lightweight nature of
    processes

    View Slide

  51. ProcessChain
    <0.87.0>  
    Link
    <0.97.0>  
    Chain Controller
    msg  
    <0.59.0>  
    Shell Process (Us)
    {:start,  <0.83.0>}  
    spawn_link  
    <0.83.0>  
    Link
    msg  
    msg  
    :shutdown  
    :shutdown  

    View Slide

  52. ProcessChain
    <0.97.0>  
    <0.38.0>  
    Chain Link
    Controller
    <0.83.0>  
    Chain Link
    <0.84.0>  
    Chain Link
    <0.77.0>  
    Chain Link
    <0.43.0>  
    Chain Link
    <0.26.0>  
    Shell (Us)

    View Slide

  53. Trying to DDOS an Elixir App

    View Slide

  54. Things I didn’t cover
    •  OTP - Open Telecom Platform
    •  Communication between nodes on
    different machines
    •  Metaprogramming
    •  Protocols
    •  The amazing pipe operator
    •  List comprehensions
    •  Much more!

    View Slide

  55. Go Try Out Elixir
    •  Install Erlang
    •  Install Elixir
    •  Instructions for installation are at
    http://elixir-lang.org/getting_started/1.html

    View Slide

  56. Trevor Brown
    @Stratus3D
    github.com/Stratus3D
    stratus3d.com
    voalte.com
    Code: 

    https://github.com/Stratus3D/
    foray_into_functional_programming_with_elixir
    Slides:
    https://speakerdeck.com/stratus3d

    View Slide

  57. Elixir vs. other
    languages
    Language   Pure   Typing   Immutable  
    Data  
    Elixir/Erlang   No   Dynamic   Yes  
    Haskell   Yes   Sta`c   Yes  
    Clojure   No   Dynamic   Yes  
    Scala   Yes   Dynamic   Par`al?  
    JavaScript   No   Dynamic   Par`al?  
    Python   No   Dynamic   Par`al?  

    View Slide

  58. Process Relationships
    •  Process spawn other
    processes


    •  Processes can be
    linked
    •  Processes can also
    monitor other
    processes
    #  Spawn  a  process  
    pid  =  spawn(worker_fun)  
    #  Link  the  process  calling  
    `link`  function  with  `pid`  
    process  
    link  pid  
    #  Monitor  `pid`  process  from  
    the  process  calling  the  
    `monitor`  function  
    monitor  pid  

    View Slide

  59. Elixir vs. Erlang
    •  Syntax Example
    defmodule  Math  do  
       def  square(x)  do  
           x  *  x  
       end  
    end  
       
    Enum.map  [1,2,3],  
    &Math.square/1  
    #=>  [1,  4,  9]  
    -­‐module(math).  
    -­‐export([square/1]).  
       
    square(X)  -­‐>  X  *  X.  
       
    lists:map(fun  math:square/1,  
    [1,  2,  3]).  
    %=>  [1,  4,  9]  
    Elixir   Erlang  

    View Slide