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

Collections in Elixir

Collections in Elixir

Elixir offers many ways to store data in a collection: tuples, lists, keyword lists,
maps and structs. Let's review each of those and discuss use cases.

Hugo Frappier

April 14, 2017
Tweet

More Decks by Hugo Frappier

Other Decks in Programming

Transcript

  1. Collections in Elixir 4 What do we get out of

    the box? 4 How they work? 4 When to use them? 4 Tips & tricks
  2. Out of the box Collection Example Tuples {:ok, "All good"}

    Lists [1, "two", :three] Keyword lists [one: 1, two: 2] Maps %{one: 1, two: 2} Structs %User{name: "John", age: 32}
  3. Tuples {:ok, "All good"} 4 Closest thing to an array

    in other languages 4 Usually used to return data from a function
  4. Tuples Often used with pattern matching: def transform(data) do ...

    {:error, 543, "You forgot something"} end case transform(data) do {:error, _code, message} -> "Error: #{message}" {:ok, data} -> data end # "Error: You forgot something"
  5. Tuples You can access a term by its position: result

    = {:weather, 24, "Montreal"} temp = elem(result, 1) # 24
  6. Tuples You can access a term by its position: result

    = {:weather, 24, "Montreal"} temp = elem(result, 1) # 24 put_elem(result, 1, temp + 2) # {:weather, 26, "Montreal"}
  7. Tuples {"Montreal", 12, 22, :norain} ✅ Very fast since stored

    in contiguous memory ✅ Great for pattern matching ! You have to know what each position holds ! Not easy to update, coupling by position
  8. Lists [1, "two", :three] 4 Collection of values 4 Stored

    in multiple locations in memory (linked list) 4 Often used in recursive functions
  9. Lists [1, 2, 3] ["one", 2, :three] ~w(one two three)

    # ["one", "two", "three"] ~w(one two three)a # [:one, :two, :three]
  10. Lists How to manipulate a list: 4 ++ & --

    operators 4 hd/1 & tl/1 Kernel functions 4 Pipe operator 4 List and Enum functions
  11. Lists [1, 2, 3] ++ [4, 5] # [1, 2,

    3, 4, 5] [1, 2] -- [2, 3] # [1]
  12. Lists [1, 2, 3] ++ [4, 5] # [1, 2,

    3, 4, 5] [1, 2] -- [2, 3] # [1] List.wrap("Hello") # ["Hello"] List.wrap(["Hello"]) # ["Hello"]
  13. Lists [1, 2, 3] ++ [4, 5] # [1, 2,

    3, 4, 5] [1, 2] -- [2, 3] # [1] List.wrap("Hello") # ["Hello"] List.wrap(["Hello"]) # ["Hello"] [ 1 | [2, 3]] # [1, 2, 3]
  14. Lists hd ["one", "two"] # "one" List.first ["one", "two"] #

    "one" tl [1, 2, 3] # [2, 3] List.last [1, 2, 3] # 3
  15. Lists hd ["one", "two"] # "one" List.first ["one", "two"] #

    "one" tl [1, 2, 3] # [2, 3] List.last [1, 2, 3] # 3 [head | tail] = [1, 2, 3] # head: 1, tail: [2, 3]
  16. Lists (enumeration) Enum.map([1, 2, 3], fn(i) -> i * 2

    end) # [2, 4, 6] Enum.reject(["one", nil, 3], &is_nil/1) # ["one", 3]
  17. Lists (enumeration) for i <- [1, 2, 3], do: i

    * 2 # [2, 4, 6] for i <- [1, 2, 3], j <- [1, 2], do: i * j # [1, 2, 2, 4, 3, 6]
  18. Lists (recursion) defmodule Math do def sum_list([], accumulator), do: accumulator

    def sum_list([head | tail], accumulator) do sum_list(tail, head + accumulator) end end Math.sum_list([1, 2, 3], 0) # 6
  19. Lists (pattern matching) def hello([head | _tail]) when is_number(head) do

    :number end def hello(_), do: :anything hello(["one"]) # :anything hello([1, "two", 3]) # :number
  20. Lists (pattern matching) def hello([x, x | _tail]), do: :twins

    def hello([head | _tail]) when is_number(head) do :number end def hello(_), do: :anything hello(["one"]) # :anything hello([1, "two", 3]) # :number hello([1, 1, 2, 3]) # :twins
  21. Lists [1, "two", :three] ✅ Easy to manipulate ✅ Fast

    when pushing on the list ! Limited pattern matching in function heads (head and/or tail)
  22. Keyword Lists [foo: "bar", hello: "you", foo: "another bar"] 4

    Associative collection (key/value) 4 Keys are atoms only, ordered and not unique 4 Implements Access behavior (list[key]) 4 Not an Erlang type 4 Special list of 2-term tuples
  23. Keyword Lists [foo: "bar", hello: "you", foo: "another bar"] [{:foo,

    "bar"}, {:hello, "you"}, {:foo, "another bar"}]
  24. Keyword Lists [foo: "bar", hello: "you", foo: "another bar"] [{:foo,

    "bar"}, {:hello, "you"}, {:foo, "another bar"}] list = [id: 1, title: "Post"] list[:title] # "Post"
  25. Keyword Lists [foo: "bar", hello: "you", foo: "another bar"] [{:foo,

    "bar"}, {:hello, "you"}, {:foo, "another bar"}] list = [id: 1, title: "Post"] list[:title] # "Post" Enum.map(list, fn({k,v}) -> k end) # [:id, :title]
  26. Keyword Lists Keyword module provides keyword list functions, such as:

    Keyword.take/2 Keyword.has_key?/2 Keyword.keys/1 Keyword.values/1 Keyword.put/3
  27. Keyword Lists Mostly used for passing options to a function:

    def say(message, options \\ []) do prefix = Keyword.get(options, :prefix, Time.utc_now) "#{prefix} #{message}" end say("World") # "14:19:53.464950 World" say("World", prefix: "Hello") # "Hello World"
  28. Keyword Lists [foo: "bar", hello: "you", foo: "bar"] ✅ Awesome

    for passing options to a function ✅ Can use all operations available to lists ! Like a regular list, we cannot pattern match on one element of a list containing multiple elements
  29. Maps %{id: 1234, title: "Maps rock!"} 4 Associative collection (key/value)

    4 "The" key-value store in Elixir 4 Keys can be in any order and of any type (unlike keyword lists) 4 Keys must be unique
  30. Maps %{id: 1234, title: "Maps rock!"} 4 Can pattern match

    on a single element 4 Can use dot notation to access a value if keys are atoms 4 It Implement the access behavior 4 Closest thing to a hash or Dict in other languages
  31. Maps (access) %{"id" => 1234, "title" => "Maps rock!", 42

    => "age"} %{id: 1234, title: "Maps rock!"}
  32. Maps (access) %{"id" => 1234, "title" => "Maps rock!", 42

    => "age"} %{id: 1234, title: "Maps rock!"} map = %{id: 1234, title: "Maps rock!"}
  33. Maps (access) %{"id" => 1234, "title" => "Maps rock!", 42

    => "age"} %{id: 1234, title: "Maps rock!"} map = %{id: 1234, title: "Maps rock!"} %{id: id} = map id # 1234
  34. Maps (access) %{"id" => 1234, "title" => "Maps rock!", 42

    => "age"} %{id: 1234, title: "Maps rock!"} map = %{id: 1234, title: "Maps rock!"} %{id: id} = map id # 1234 map.title <> map[:title] # "Maps rock!Maps rock!"
  35. Maps (merge) post = %{id: 1234, title: "Maps rock!"} %{post

    | title: "MAPS ROCK!"} # %{id: 1234, title: "MAPS ROCK!"}
  36. Maps (merge) post = %{id: 1234, title: "Maps rock!"} %{post

    | title: "MAPS ROCK!"} # %{id: 1234, title: "MAPS ROCK!"} %{post | what: "Boo"} # ** (KeyError) key :what not found # in: %{id: 1234, title: "Maps rock!"}
  37. Maps (merge) post = %{id: 1234, title: "Maps rock!"} %{post

    | title: "MAPS ROCK!"} # %{id: 1234, title: "MAPS ROCK!"} Map.put(post, :what, "Boo") # %{id: 1234, title: "Maps rock!", what: "Boo"}
  38. Maps Map module provides functions for maps, such as: Map.take/2

    Map.has_key?/2 Map.keys/1 Map.values/1 Map.put/3
  39. Maps (pattern matching) def salutation(%{gender: "m", name: name}) do "Dear

    Mr #{name}" end def salutation(%{gender: "f", name: name}) do "Dear Madam #{name}" end salutation(%{name: "Smith", gender: "m", age: 32}) # "Dear Mr Smith"
  40. Maps (pattern matching) def square?(%{width: x, height: x}), do: true

    def square?(_), do: false square?(%{width: 10, height: 10}) # true square?(%{width: 3, height: 2}) # false
  41. Maps %{id: 1234, title: "Maps rock!"} ✅ Awesome for passing

    data to a function ✅ Great for pattern matching in function heads ! Unstructured and untyped data store ! No indifferent access (if key is an atom, you cannot access it's value using a string)
  42. Structs %Post{id: 1234, title: "Maps rock!"} 4 Extension built on

    top of a map (not in Erlang) 4 Compile-time guarantees that only the fields defined through defstruct will be allowed in the struct
  43. Structs defmodule User do defstruct name: "John", age: 27 end

    user = %User{name: "Tim"} # %User{age: 27, name: "Tim"}
  44. Structs IO.inspect user # %User{age: 27, name: "Tim"} IO.inspect user,

    structs: false # %{__struct__: User, age: 27, name: "John"}
  45. Structs user = %User{name: "Tim"} # %User{age: 27, name: "Tim"}

    %{user | id: 1} # ** (KeyError) key :id not found # in: %User{age: 27, name: "Tim"}
  46. Structs user = %User{name: "Tim"} # %User{age: 27, name: "Tim"}

    %{user | id: 1} # ** (KeyError) key :id not found # in: %User{age: 27, name: "Tim"} Map.put(user, id: 1) # %{__struct__: User, age: 27, id: 1, name: "Tim"}
  47. Structs %Post{id: 1234, title: "Maps rock!"} ✅ All the advantages

    of a map ✅ Type checking enforced at compile time ! Be careful when using Map functions, you could end- up with a struct disconnected from its definition
  48. Collections in Elixir What When Tuples Returning data from a

    function Lists For a collection of items Keyword lists Passing options to a function Maps Flexible key/value store Structs Typed/fixed key/value store