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}