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

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. Elixir? •  Built on top of the Erlang VM • 

    Compiles down to BEAM bytecode •  Helps to have an understanding of 
 Erlang and the underlying VM
  2. 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
  3. 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
  4. 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
  5. Myth “Elixir is just CoffeeScript for Erlang” •  Elixir provides

    better tooling, first-class docs, and easier metaprogramming than Erlang •  Additional data types
  6. 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
  7. 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
  8. Binaries and Numbers #  Binaries   <<97,98,99>>   <<"abc">>  

        #  Integers   12       #  Floats   23.5  
  9. 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  
  10. 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]  
  11. 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       <<?a,  ?b,  ?c>>  #=>  "abc"   [?a,  ?b,  ?c]      #=>  'abc'  
  12. #  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
  13. Modules •  Code organization is done via modules and functions.

    defmodule  Ping  do      @moduledoc  """      Sample  module      """          #  Add  functions  here   end    
  14. 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    
  15. 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  
  16. 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}    
  17. 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  
  18. 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}    
  19. 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}  
  20. 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é"}  
  21. 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    
  22. 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  
  23. 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
  24. 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é  
  25. 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")  
  26. 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é"]  
  27. 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"]  
  28. •  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
  29. •  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  
  30. Another Example def  factorial(0)  do      1   end

          def  factorial(number)  do      factorial(number  -­‐  1)  *  number   end   •  A recursive function that computes factorials
  31. 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  
  32. 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
  33. 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
  34. 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
  35. The Erlang VM CPU   CPU   CPU   CPU

      Thread   Thread   Thread   Thread   Virtual  Machine  Process   Process   Process   Process   Process   Process   Process  
  36. 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
  37. 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
  38. Processes are Actors •  Implements the actor model •  Processes

    encapsulate state •  Elixir’s processes are more object-oriented than objects in object-oriented languages
  39. 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  
  40. 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  
  41. 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
  42. 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  
  43. 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)
  44. 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!
  45. Go Try Out Elixir •  Install Erlang •  Install Elixir

    •  Instructions for installation are at http://elixir-lang.org/getting_started/1.html
  46. 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?  
  47. 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  
  48. 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