Collections

Tuple

tp = {:ok, "value"}

Tuple elements are usually accessed via pattern matching:

{:ok, value} = tp
value
# "value"

But they can also be accessed using elem:

elem(tp, 0)
# :ok

Lists

xs = [1, 2, 3]
hd(xs)
# 1
tl(xs)
# [2, 3]
[first | rest] = xs
rest
# [2, 3]
[first | rest]
# [1, 2, 3]
xs ++ [4]
# [1, 2, 3, 4]

Accessing elements by index is an O(n):

Enum.at(xs, 1)
# 2
Enum.at(xs, 3)
# nil
Enum.at(xs, 3, :default_val)
# :default_val

Operations map, filter on Lists are more common:

Enum.map([1, 2, 3], fn x -> x * 2 end)
# [2, 4, 6]
Enum.filter([1, 2, 3], &rem(&1, 2) == 0)
# [2]
Enum.reduce([1, 2, 3, 4], 0, &+/2)
# 10

Keyword Lists

A keyword list is a list of two-element tuples where the first element (the key) is an atom.

foo = [{:a, 1}, {:a, 2}, {:b, nil}]
# [a: 1, a: 2, b: nil]

Elixir's keyword lists are conceptually very similar alists in Emacs Lisp.

'((first-name . "John") (last-name . "Doe") (age . 30))

Elixir provides a more concise syntax sugar for keyword lists:

foo = [a: 1, a: 2, b: nil]
# [a: 1, a: 2, b: nil]
hd(foo)
# {:a, 1}
foo[:a]
# 1
Keyword.get_values(foo, :a)
# [1, 2]
Keyword.has_key?(foo, :b)
# true
foo[:c]
# nil

Keyword Lists are usually small(access is O(n)).

A great use case is for passing optional named arguments to functions:

String.split "hello world  ", " ", trim: true
# ["hello", "world"]
defmodule Greeter do
  def greet(name, opts \\ []) do
    greeting = Keyword.get(opts, :greeting, "Hello")
    "#{greeting}, #{name}!"
  end
end

Greeter.greet("Alice")
# "Hello, Alice!"

Greeter.greet("Alice", greeting: "Hi")
# "Hi, Alice!"

This is usually achieved by using a map in other languages. In Clojure it's keyword arguments:

(defn greet [name & {:keys [greeting] :or {greeting "Hello"}}]
  (str greeting ", " name "!"))

(greet "Alice" )
; "Hello, Alice!"
(greet "Alice" :greeting "Hi")
; "Hi, Alice!"

Map

map = %{:name => "Alice", "age" => 7, 7 => nil, true => false}
# %{7 => nil, true => false, :name => "Alice", "age" => 7}

map["age"]
# 7

map[:name]
# "Alice"

map.name
# "Alice"

map[:no_such_key]
# nil

Map.get(map, :no_such_key, :value)
# :value

Map.fetch(map, :name)
# {:ok, "Alice"}

Map.fetch(map, :no_such_key)
# :error

Map.new([{:name, "Alice"}, {map, nil}])
# %{
#   :name => "Alice",
#   %{7 => nil, true => false, :name => "Alice", "age" => 7} => nil
# }
map = %{key: 1}
# %{key: 1}

%{map | key: 2}
# %{key: 2}
%{map | k2: 1}
# ** (KeyError) key :k2 not found in: %{key: 1}

Map.put(map, :key, 2)
# %{key: 2}
Map.put(map, :key2, 2)
# %{key: 1, key2: 2}
%{a: 1, b: 2} |> Enum.map(fn {k, v} -> {k, v * 2} end)
# [a: 2, b: 4]
%{a: 1, b: 2} |> Enum.map(fn {k, v} -> {k, v * 2} end) |> Map.new
%{a: 2, b: 4}

MapSet

set = MapSet.new(["a", "b", "c"])

MapSet.member?(set, "b")
# true

MapSet.put(set, "b")
# MapSet.new(["a", "b", "c"])

MapSet.delete(set, "d")
# MapSet.new(["a", "b", "c"])

MapSet.union(set, MapSet.new(["c", "d"]))
# MapSet.new(["a", "b", "c", "d"])

MapSet.intersection(set, MapSet.new(["c", "d"]))
# MapSet.new(["c"])

MapSet.difference(set, MapSet.new(["c", "d"]))
# MapSet.new(["a", "b"])

MapSet.subset?(MapSet.new(["c"]), set)
# true

Struct

defmodule User do
  defstruct name: "", age: nil
end

user = %User{name: "Alice", age: 7}

user.age
# 7

%User{ user | age: 8}
# %User{name: "Alice", age: 8}
defmodule User do
  defstruct [:name, :age]

  def child?(%User{age: age}) do
    is_integer(age) and age < 18
  end
end

User.child?(%User{name: "Alice", age: 7})
# true

Working with enumerable data types

Enum provides a wide range of functions for working with enumerable data types. Combined with pipe operator, it can pretty clear and concise.

List comprehension is even more concise for specific operations, namely map and filter.

1..10
|> Enum.filter &rem(&1, 2) == 0
|> Enum.map &(&1*&1)
for x <- 1..10, rem(x, 2) == 0, do: x * x

There is also into:

%{a: 1, b: 2}
|> Enum.map(fn {k, v} -> {k, v * 2} end)
|> Map.new
for {k, v} <- %{a: 1, b: 2}, into: %{}, do: {k, v * 2}