with and for

with Keyword

with chains a sequence of pattern matches. It succeeds when every step matches and gives you back the value of the last expression. If any step fails, with falls through to the else block (or returns the value that did not match).

Here is the smallest possible example:

iex> with {:ok, n} <- {:ok, 42} do
...>   "got #{n}"
...> end
"got 42"

The operator reads as "pattern match against". It is very close to =, but when the match fails, with stops instead of raising.

A more realistic example chains two lookups that both have to succeed:

iex> user = %{name: "Alice", city: "Berlin"}
iex> with {:ok, name} <- Map.fetch(user, :name),
...>      {:ok, city} <- Map.fetch(user, :city) do
...>   "#{name} lives in #{city}"
...> else
...>   :error -> "missing data"
...> end
"Alice lives in Berlin"

If either Map.fetch/2 returns :error, the else branch runs.

The arrow uses pattern matching, so every shape you can write on the left side of = also works here.

for Comprehensions

for comprehensions iterate over one or more enumerables (lists, maps, ranges) and build a new collection from the results.

Here is the smallest example:

iex> for num <- [1, 2, 3, 4, 5], do: num * num
[1, 4, 9, 16, 25]

for walks each number in the list, squares it, and collects the results into a new list.

You can add a filter expression after the generator. Elements for which the filter returns false are skipped:

iex> for num <- [1, 2, 3, 4, 5], rem(num, 2) == 1, do: num * num
[1, 9, 25]

Only odd numbers survive the filter, so the result contains their squares.

For most transformations a function from the Enum module reads more naturally than a for comprehension. Reach for for when you want to combine several generators or filters in one place.