Skip to content

Latest commit



1239 lines (931 loc) · 32.6 KB

File metadata and controls

1239 lines (931 loc) · 32.6 KB

Learn Functional Programming with Elixir

Run Elixir's interactive shell:

$ iex

Create a "Hello, World!" program in the interactive shell:

iex> IO.puts "Hello, World!"
Hello, World!

There are two extensions for Elixir files:

  1. .ex for files to be compiled
  2. .exs for script files to be executed directly

Create a "Hello, World!" program in a file called hello_world.exs:

IO.puts "Hello, World!"

Run the program using Elixir:

$ elixir hello_world.exs
Hello, World!

Thinking Functionally

Elixir uses immutable data structures. The original list object is never modified, but new list objects are returned.

> list = [1, 2, 3, 4]
[1, 2, 3, 4]
> List.delete_at(list, -1)
[1, 2, 3]
> list ++ [1]
[1, 2, 3, 1]
> list
[1, 2, 3, 4]

delete_at is not a method of the list itself, but a function of the List module, which can be applied to lists, and that returns new lists instead of modifying an underlying list.

A pure function only depends on its parameters:

> twice = fn (n) -> n * 2 end
> twice.(3)

Instead of maintaining internal state with classes, modules provide functions that operate on data without manipulating it, but returning the modified versions of that data (set.exs):

defmodule MySet do
  defstruct items: []
  def push(set = %{items: items}, item) do
    if Enum.member?(items, item) do
      %{set | items: items ++ [item]}

This module can be used as follows:

$ iex set.exs
> set = %MySet{}
> set = MySet.push(set, "apple")
> new_set = %MySet{}
> new_set = MySet.push(new_set, "pie")
> IO.inspect MySet.push(set, "apple")
%MySet{items: ["apple"]}
> IO.inspect MySet.push(new_set, "apple")
%MySet{items: ["pie", "apple"]}

Functions can be used as arguments. Here, the upcase function of the String module is applied to a list of strings:

>["dogs", "cats", "mice"], &String.upcase/1)
["DOGS", "CATS", "MICE"]

The result of a function call can be used as the parameter to a subsequent function call using the pipe operator |>. Instead of using nested function calls:

> String.split(String.downcase(String.trim("  THIS IS A TEST  ")))
["this", "is", "a", "test"]

The pipe operator can be used to chain the function calls together:

> "  THIS IS A TEST  " |> String.trim |> String.downcase |> String.split
["this", "is", "a", "test"]

Instead of explicitly stating the control flow, this recursive function only defines the steps to transform the data (stringlist.exs):

defmodule StringList do
  def upcase([]), do: []
  def upcase([first | rest]), do: [String.upcase(first) | upcase(rest)]

Which can be used as follows:

$ iex stringlist.exs
> StringList.upcase(["foo", "bar", "baz", "bum"])
["FOO", "BAR", "BAZ", "BUM"]

Higher-order functions simplify commonly used operations such as processing lists of items:

> list = ["foo", "bar", "baz", "bum"]
>, &String.upcase/1)
["FOO", "BAR", "BAZ", "BUM"]

Working with Variables and Functions

Basic data types:

  • string: "Hello, World", "foobar", "/home/patrick"
  • integer: 42, 123, 10_000
  • float: 3.14, 9.95, -10.01
  • boolean: true, false
  • atom: :ok, :error, :exit (identifiers)
  • tuple: {:ok, "Hello"}, {1, 2, 3} (collections of known sizes)
  • list: [1, 2, 3], ["foo", "bar", "baz"] (collections of unknown sizes)
  • map: (lookup values by key)
    • %{id: 123, name: "John"}
    • %{12 => "Alice"}
  • nil: nil (absence of a value)


  • +, -, /, *: basic arithmetic
  • ==, !=, <, <=, >, >=: comparison
  • ++: list concatenation
    • [1, 2, 3] ++ [4, 5] -> [1, 2, 3, 4, 5]
  • <>: binary concatenation (strings and binaries)
    • "what" <> "ever" -> "whatever"

There are two versions of logical operators:

  • and, or, not working on boolean expressions
    • the left side of the operator must be a boolean expression
  • &&, ||, ! working on "truthy"/"falsy" expressions
    • "falsy": false and nil
    • "truthy": everything that is not "falsy"


> true and false
> true or false
> true and not false

> nil && 1
> true && "Hello"
> false || 4
> 1 && !5

Anonymous functions have no global name, but can be bound to variables for reference:

> say_hi_to = fn name -> "Hello, #{name}!" end
> say_hi_to.("Alice")
"Hello, Alice!"
> say_hi_to.("Bob")
"Hello, Bob!"

The say_hi_to function uses string interpolation syntax (#{e}). The expression e is coecred into a string.

Anonymous functions (or "lambdas") can take multiple parameters:

> calc_circumference = fn height, width -> 2 * height + 2 * width end
> calc_circumference.(3, 4)

They can also take other functions as arguments:

> apply = fn a, b, op -> op.(a, b) end
> average = fn a, b -> (a + b) / 2 end
> apply.(3, 7, average)

Here, the average function is passed as the op parameter to the apply function.

A closure remembers the variables of its lexical scope:

> message = "Hello, World!"
> say_hi = fn -> Process.sleep(1000); IO.puts(message) end
> message = "Something else"
> spawn(say_hi)
> message = "Whatever"
Hello, World!

Even though the message binding was overwritten, the say_hi function still remembers the value "Hello, World!".

Modules are named using aliases (String) or atoms (:"Elixir.String"). Aliases transform into atoms during compile time:

> String == :"Elixir.String"

Aliases start with a capital letter and may only contain ASCII characters. Because of the ., the atom Elixir.String above is quoted.

Named functions of a module can be accessed using the dot operator. Parentheses are optional.

> String.upcase "without parentheses"
> String.upcase("with parentheses")

The following basic modules contain many commonly used named functions:

  • String: text
    • String.capitalize("hi") -> "HI"
    • String.downcase("HI") -> "hi"
  • Integer: integers
    • Integer.parse("123") -> {123, ""}
    • Integer.to_string(123) -> "123"
    • Integer.digits(1987) -> [1, 9, 8, 7]
  • Float: floating point numbers
    • Float.ceil(3.7) -> 4.0
    • Float.floor(3.7) -> 3.0
    • Float.round(3.4999) -> 3.0
  • IO: input/output
    • IO.puts("Hello!") -> outputs "Hello!"
    • IO.gets("Name? ") -> outputs "Name? ", returns user input
    • IO.inspect({:ok, 123}) outputs {:ok, 123}
  • Kernel: common functions, can be used without qualifier
    • div(3, 2) -> 1
    • rem(3, 2) -> 1
    • is_number("foo") -> false

Modules should be defined in their own file (checkout.ex):

defmodule Checkout do def total_cost(price, tax_rate) do price * (tax_rate + 1) end end

The module name starts with an uppercase character, the file name is in all lowercase. Module names use CamelCase, function and variable names use snake_case.

The function automatically returns the evaluation of its last expression, no return statement is required.

A module can be compiles as follows in iex:

> c("checkout.ex")

A list of the compiled modules ([Checkout]) is returned. The module then can be used as any other module:

> Checkout.total_cost(100, 0.2)

The function can also be defined on a single line within the module:

def total_cost(price, tax_rate), do: price * (tax_rate + 1)

Notice the comma before and the colon after the do keyword, as well as the lack of an end keyword.

Name collisions can be avoided by prefixing the module's name with the application's name:

defmodule Ecommerce.Checkout do

The file checkout.ex then needs to be placed in a directory called ecommerce.

Then the fully qualified name has to be used to invoce the module's function:

> Ecommerce.Checkout.total_cost(100, 0.2)

A module can be imported so that its functions can be invoked without fully qualify their names:

> import Checkout
> total_cost(100, 0.2)

It's also possible to only import a subset of a module's functions into the current namespace (task_list.ex):

defmodule TaskList do

  import File, only: [write: 3, read: 1]

  @file_name ""

  def add(task_name) do
    task = "[ ] #{task_name}\n"
    write(@file_name, task, [:append])

  def show_list do


The arity of the functions being imported needs to be defined explicitly (write: 3, read: 1); it states the number of parameters the function expects.

@file_name is a module attribute, here being used as a constant. The module can be used as follows:

$ iex
> c("task_list.ex")
> TaskList.add("wash the dishes")
> TaskList.add("walk the dog")
> TaskList.show_list()
{:ok, "[ ] wash the dishes\n[ ] walk the dog\n"}

The arity needs to be stated when referring to named functions using the capturing operator &:

> upcase = &String.upcase/1
> upcase.("hi")

The total_cost function from above can be re-defined as follows:

> total_cost = &(&1 * &2)
> total_cost.(10, 2)

The function's parameters can be referred to using &1, &2, etc. in their order used at the invocation.

The parentheses are optional:

> total_cost = & &1 * &2 

The capture syntax is shorter to type but might be less clear to read due to the lack of parameter names.

Using Pattern Matching to Control the Program Flow

Pattern matching is not the same as assignment:

> x = 1
> 1 = x
> 2 = x
** (MatchError) no match of right hand side value: 1

Unlike Erlang, Elixir allows rebinding variables:

> x = 1
> x = 2

The pin operator ^ makes sure that a match is performed against the value, which avoids re-binding:

> x = 3
> x = 4
> ^x = 4
> ^x = 5
** (MatchError) no match of right hand side value: 5

Pattern matching can be used to unpack parts of values from expressions (destructuring):

> "Patrick " <> lastname = "Patrick Bucher"
"Patrick Bucher"
> lastname

Tuples can be used to signify success and error of an operation:

> {:ok, value} = {:ok, 13}
{:ok, 13}
> {:ok, value} = {:error, :not_found}
** (MatchError) no match of right hand side value: {:error, :not_found}

In general, functions should return {:ok, result} in case of success, and {:error, :error_type} in case of failure.

The underscore _ matches any value, which is then discarded:

> {_, _, third, _} = {13, 17, 22, 18}
{13, 17, 22, 18}
> third

List elements can be separated using the | operator:

> [head | tail] = [1, 2, 3]
[1, 2, 3]
> head
> tail
[2, 3]

It is possible to match multiple elements on the left side:

> [a, b | tail] = [1, 2, 3]
[1, 2, 3]
> a
> b
> tail

Map literals can be written in two ways:

> user = %{email: "", password: "topsecret"}
%{email: "", password: "topsecret"}

> user = %{:email => "", :password => "topsecret"}
%{email: "", password: "topsecret"}

The first way is more compact, the second way allows for any value in the key (not just atoms).

Map values can be extracted by matching keys:

> %{password: password} = user
%{email: "", password: "topsecret"}
> password

Either syntax works:

> %{:password => password} = user
%{email: "", password: "topsecret"}
> password

Maps can not only be matched for certain keys, but also for values:

> %{email: "", password: password} = user
%{email: "", password: "topsecret"}
> password

Matching and extraction can also be combined:

> %{email: email = ""} = user
%{email: "", password: "topsecret"}
> email

Pinning accomplishes the same in two steps, which is useful if the value to be matched has to be computed beforehand:

> email = ""
> %{email: ^email} = user
%{email: "", password: "topsecret"}
> email

Keyword lists contain two-element tuples that may contain duplicate keys:

> animals = [dog: "Bello", cat: "Pussy", dog: "Doggo", cat: "Whiskers"]
[dog: "Bello", cat: "Pussy", dog: "Doggo", cat: "Whiskers"]

They are also used in imports; functions with the same name but different arities can be imported:

> import String, only: [pad_leading: 2, pad_leading: 3]

Structs are maps with a fixed set of keys to represent things such as calendar dates. They can be matched like maps:

> birthday = ~D[1987-06-24]
> %{year: year} = birthday
> year

The match can be refined using the type name:

> %Date{year: year} = birthday
> year
> %Date{year: year} = %{year: 2005}
** (MatchError) no match of right hand side value: %{year: 2005}

Sigils like ~D are syntactic sugar to build objects from text representations. Other examples are the word list sigil ~w or the reguar expression sigil ~r:

> ~w(Dilbert Alice Wally)
["Dilbert", "Alice", "Wally"]

> pattern = ~r/^clean:$/

Multiple functions with the same signature can be used for pattern matching:

defmodule NumberCompare do
  def greater(number, other_number) do
    check(number >= other_number, number, other_number)

  defp check(true, number, _), do: number
  defp check(false, _, other_number), do: other_number

The check function has multiple clauses, defined with defp, so that they are not visible from outside of the module. Clauses belonging to the same function must stand in a sequence that must not be interrupted by other function's clauses.

Default values for function parameters can be defined using the \\ operator:

defmodule Checkout do
  def total_cost(price, quantity \\ 10), do: price * quantity

Internally, two functions are created: one expecting the quantity parameter, and one having it set already. Only one default value can be used for a function definition.

The above function can be captured and used as follows:

> total_default = &Checkout.total_cost/1
> total = &Checkout.total_cost/2
> total_default.(2)
> total_default.(2, 10)
value per function.

Guard clauses can be used to control which function clause is executed:

defmodule NumberCompare do
  def greater(number, other_number) when number >= other_number, do: number
  def greater(_, other_number), do: other_number

The first clause is protected with a guard expression. If it doesn't match, the first clause is taken.

Pattern matching and guards can be used in anonymous functions, too:

number_compare = fn
  number, other_number when number >= other_number -> number
  _, other_number -> other_number

number_compare.(1, 8)
number_compare.(7, 3)

Only authorized functions that are pure can be used in guard expressions, e.g. those from the Integer module (even_or_odd.ex):

defmodule EvenOrOdd do
  require Integer

  def check(number) when Integer.is_even(number), do: "even"
  def check(number) when Integer.is_odd(number), do: "odd"

> c("even_or_odd.ex")
> EvenOrOdd.check(3)
> EvenOrOdd.check(2)

Here, require has to be used, so that the Integer functions can be used at compile-time. Definitions added using require are lexically scoped, and, thus, only available in the respective scope.

Guards can be re-used by defining them using the defguard keyword:

defmodule Checkout do
  defguard is_rate(value) when is_float(value) and value >= 0 and value <= 1
  defguard is_cents(value) when is_integer(value) and value >= 0

  def total_cost(price, tax_rate) when is_cents(price) and is_rate(tax_rate) do
    price + tax_cost(price, tax_rate)

  def tax_cost(price, tax_rate) when is_cents(price) and is_rate(tax_rate) do
    price * tax_rate

> c("checkout.ex")
> Checkout.tax_cost(40, 0.1)
> Checkout.total_cost(40, 0.1)

Multiple pattern-matching clauses can be checked using a case expression:

user_input = IO.gets "What's your IQ? "
case Integer.parse(user_input) do
  :error -> IO.puts "Malformed IQ: #{user_input}"
  {iq, _} -> IO.puts "Your IQ is #{iq}"

The case expression returns a value:

user_input = IO.gets "What's your IQ? "
output = case Integer.parse(user_input) do
  :error -> "Malformed IQ: #{user_input}"
  {iq, _} -> "Your IQ is #{iq}"
IO.puts output

Different variables and/or values can be checked usign cond:

{weight_kg, _} = Integer.parse(IO.gets("Weight [kg]: "))
{height_cm, _} = Integer.parse(IO.gets("Height [cm]: "))

height_m = height_cm / 100
height_square = height_m * height_m
bmi = weight_kg / height_square

result = cond do
  bmi < 20 -> "BMI #{bmi} is underweight"
  bmi <= 25 -> "BMI #{bmi} is good"
  bmi > 25 -> "BMI #{bmi} is overweight"

IO.puts result

if/else and unless/else can be used for simple (binary) checks:

a = 13
b = 3

bigger = if a > b do

smaller = unless a > b do

IO.puts "bigger: #{bigger}"
IO.puts "smaller: #{smaller}"

They are actually implemented as macros and can be used as follows, too:

> bigger = if(a > b, do: a, else: b)
> smaller = unless(a > b, do: a, else: b)

Diving into Recursion

The boundary clause (trivial case) must always be listed before the case that applies repeatedly (general case):

defmodule Sum do
  def up_to(0), do: 0
  def up_to(n), do: n + up_to(n - 1)

Lists are usually processed element by element by splitting the list's head from its tail:

defmodule Math do
  def sum([]), do: 0
  def sum([head | tail]), do: head + sum(tail)

List can also be built by prepending elements using the [head | tail] syntax:

defmodule EnchanterShop do
  def test_data do
      %{title: "Longsword", price: 50, magic: false},
      %{title: "Healing Potion", price: 60, magic: true},
      %{title: "Rope", price: 10, magic: false},
      %{title: "Dragon's Spear", price: 100, magic: true},
  @enchanter_name "Edwin"
  def enchant_for_sale([]), do: []
  def enchant_for_sale([item = %{magic: true} | incoming_items]) do
    [item | enchant_for_sale(incoming_items)]
  def enchant_for_sale([item | incoming_items]) do
    new_item = %{
      title: "#{@enchanter_name}'s #{item.title}",
      price:  item.price * 3,
      magic: true
    [new_item | enchant_for_sale(incoming_items)]

The second enchant_for_sale clause only matches on items having set the magic key to true. The third enchant_for_sale clause will therefore, implicitly, only process the items having set magic to false.

The items of a map can also be accessed using their keys, either using square brackets (missing keys return nil) or using the dot notation (missing keys raise a KeyError)::

> [head | tail] = EnchanterShop.test_data

> head[:magic]
> head[:price]
> head[:title]
> head[:size]

> head.magic
> head.price
> head.title
> head.size
** (KeyError) key :size not found in: %{magic: false, price: 50, title: "Longsword"}

A factorial function can be implemented using the decrease and conquer technique, where the problem left to be solved is decreased on every step:

defmodule Factorial do
  def of(0), do: 1
  def of(n) when n > 0, do: n * of(n - 1)

A sort function, such as merge sort, can be implemented using the divide and conquer technique, where the initial problem is divided into easier to solve sub-problems:

defmodule Sort do

  def ascending([]), do: []
  def ascending([a]), do: [a]
  def ascending(list) do
    half_size = div(Enum.count(list), 2)
    {list_a, list_b} = Enum.split(list, half_size)
    merge(ascending(list_a), ascending(list_b))

  defp merge([], list_b), do: list_b
  defp merge(list_a, []), do: list_a
  defp merge([head_a | tail_a], list_b = [head_b | _]) when head_a <= head_b do
    [head_a | merge(tail_a, list_b)]
  defp merge(list_a = [head_a | _], [head_b | tail_b]) when head_a > head_b do
    [head_b | merge(list_a, tail_b)]


Tail-recursive only have a single function call as their last step. Such functions can be optimized by the compiler. They often use additional accumulator parameters:

defmodule TRFactorial do
  def of(n), do: factorial_of(n, 1)
  defp factorial_of(0, acc), do: acc
  defp factorial_of(n, acc) when n > 0, do: factorial_of(n - 1, n * acc)

The problem is reduced as n shrinks, and the accumulator storing the intermediate result, grows.

Recursive structures, such as linked web pages or the file system, are best processed using recursive functions.

defmodule Navigator do

  @max_depth 3

  def navigate(dir) do
    expanded_dir = Path.expand(dir)
    go_through([expanded_dir], 0)

  defp go_through([], _current_depth), do: nil
  defp go_through(_dirs, current_depth) when current_depth > @max_depth, do: nil
  defp go_through([content | rest], current_depth) do
    print_and_navigate(content, File.dir?(content), current_depth)
    go_through(rest, current_depth)

  defp print_and_navigate(_dir, false, _current_depth), do: nil
  defp print_and_navigate(dir, true, current_depth) do
    IO.puts dir
    children_dirs =!(dir)
    go_through(expand_dirs(children_dirs, dir), current_depth + 1)

  defp expand_dirs([], _relative_to), do: []
  defp expand_dirs([dir | dirs], relative_to) do
    expanded_dir = Path.expand(dir, relative_to)
    [expanded_dir | expand_dirs(dirs, relative_to)]


The depth of the recursion is restricted, adding a boundary to the recursive problem.

Using Higher-Order Functions

Higher-Order functions accept other functions as parameters, and/or return functions.

The each function applies the given function (second parameter) to each item in the list (first parameter):

defmodule MyList do
  def each([], _function), do: nil
  def each([head | tail], function) do
    each(tail, function)

It can be used as follows to output all the list items:

> c("my_list.ex")
> MyList.each(["Alice", "Bob", "Mallory"], fn item -> IO.puts item end)

The map function transforms a given list by applying the given function to each element:

def map([], _function), do: []
def map([head | tail], function) do
  [function.(head) | map(tail, function)]

>["Alice", "Bob", "Mallory"], fn item -> String.length(item) end)
[5, 3, 7]

>["Alice", "Bob", "Mallory"], &String.length/1)
[5, 3, 7]

> discount = fn item -> update_in(item.price, &(&1 * 0.9)) end
> items = [%{name: "Beer", price: 2.50}, %{name: "Water", price: 1.20}]
>, discount)
[%{name: "Beer", price: 2.25}, %{name: "Water", price: 1.08}]

The reduce function computes an aggregate value over a given list. The accumulator is used both for storing intermediate results, and to initialize a neutral element:

def reduce([], acc, _function), do: acc
def reduce([head | tail], acc, function) do
  reduce(tail, function.(head, acc), function)

> MyList.reduce([1, 2, 3, 4], 0, &(&1 + &2))
> MyList.reduce([1, 2, 3, 4], 1, &(&1 * &2))
> MyList.reduce([1, 2, 3, 4], 0, &+/2)
> MyList.reduce([1, 2, 3, 4], 1, &*/2)

The filter function applies a predicate function on every item, and produces a list only consisting of the items for with the predicate holds true:

def filter([], _function), do: []
def filter([head | tail], function) do
  if function.(head) do
    [head | filter(tail, function)]
    filter(tail, function)

> MyList.filter([1, 2, 3, 4, 5, 6], &(rem(&1, 2) == 0))
[2, 4, 6]

These functions, and many more, are all available in the Enum module:

  • Enum.each/2
  • Enum.reduce/2 (without neutral element)
  • Enum.reduce/3 (with neutral element)
  • Enum.filter/2
  • Enum.count/1
  • Enum.sort/2
  • Enum.sum/1
  • Enum.uniq/1
  • Enum.member?/2
  • Enum.join/2

Enum's functions work with any type respecting the Enumerable protocol (lists, maps, tuples, etc.).

Some functions require multiple functions as parameters. The group_by function needs one function to extract a grouping criterion, and one function to extract an identifier from the items:

employees = [
  %{department: :engineering, name: "Dilbert"},
  %{department: :management, name: "Pointy Haired Boss"},
  %{department: :engineering, name: "Wally"},
  %{department: :hr, name: "Catbert"},
  %{department: :marketing, name: "Ted"},
  %{department: :management, name: "Egghead"},
  %{department: :engineering, name: "Alice"},

departments = Enum.group_by(employees, &(&1.department), &(&
IO.inspect departments


$ elixir departments.exs %{ engineering: ["Dilbert", "Wally", "Alice"], hr: ["Catbert"], management: ["Pointy Haired Boss", "Egghead"], marketing: ["Ted"] }

List comprehensions are created using the for form and one or mor generator expressions:

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

Multiple generator expressions can be used to build the carthesian product (i.e. all combinations) of the listed items:

> for a <- ["1", "2", "3"], b <- ["a", "b", "c"], do: a <> b
["1a", "1b", "1c", "2a", "2b", "2c", "3a", "3b", "3c"]

Conditions can be used to restrict the items going into the result list:

> for i <- [1, 2, 3, 4, 5, 6], rem(i, 2) == 0, do: i
[2, 4, 6]

Functions can be composed using the pipe operator |>:

> "test" |> String.first |> String.upcase

> "to kill a mockingbird"
  |> String.split
  |> Enum.join(" ")
"To Kill A Mockingbird"

Instead of currying, functions can be partially applied using closures. The function is partially applied with alphabet, and can later be used by calling partial.

defmodule WordBuilder do
  def build(alphabet, positions) do
    partial = fn at ->, at) end
    letters =, partial)

The same can be expressed using function-capturing syntax:

defmodule WordBuilder do
  def build(alphabet, positions) do
    letters =, &(, &1)))

Infinite collections of data are possible using lazy evaluation and the Stream module. Simple streams can be expressed using the range literal:

> numbers = 1..10
> Enum.each(1..10, &IO.puts/1)

The factorial can be computed as follows using ranges:

defmodule Factorial do
  def of(0), do: 1
  def of(n) when n > 0 do
      |> Enum.take(n)
      |> Enum.reduce(&(&1 * &2))

Ranges are only evaluated as needed. An infinite stream of numbers can be created as follows:

> integers = Stream.iterate(1, fn prev -> prev + 1 end)
> Enum.take(integers, 3)
[1, 2, 3]

Which can be used to re-factor the factorial implementation from above:

defmodule Factorial do
  def of(0), do: 1
  def of(n) when n > 0 do
    Stream.iterate(1, fn prev -> prev + 1 end)
      |> Enum.take(n)
      |> Enum.reduce(&(&1 * &2))

It's possible to cycle endlessly through an enumeration (halloween.ex):

defmodule Halloween do
  def give_candy(kids) do
    ~w(chocolate jelly mint)
      |> Stream.cycle

> c("halloween.ex")
Halloween.give_candy(~w(Alice Bob Carol Dan Enia Frank))
  {"chocolate", "Alice"},
  {"jelly", "Bob"},
  {"mint", "Carol"},
  {"chocolate", "Dan"},
  {"jelly", "Enia"},
  {"mint", "Frank"}

There are two ways a list of items can be processed by a pipeline of functions:

  1. Eager: Every function processes the whole list and sends the result to the next function.
  2. Lazy: A function only processes a finite amount of items and sends the partial result to the next function.

While the first approach makes sense for very small tasks, the second approach produces results earlier for further processing.

ScrewsFactory (screws_factory.ex) simulates the process of producing screws from metal pieces, here with the first (eager) approach:

defmodule ScrewsFactory do

  def run(pieces) do
    |> Enum.each(&output/1)

  defp add_thread(piece) do
    piece <> "--"

  defp add_head(piece) do
    "o" <> piece

  defp output(screw) do


> c("screws_factory.ex")
> metal_pieces = Enum.take(Stream.cycle(["-"]), 10)
["-", "-", "-", "-", "-", "-", "-", "-", "-", "-"]

First, nothing happens, then, all of a sudden, the ten screws appear.

The run function can be changed as follows in order to use lazy evaluation:

defmodule ScrewsFactory do

  def run(pieces) do
    |> Enum.each(&output/1)

# ...

By chunking the list of pieces, each function can process a partial list instead of only a single element:

defmodule ScrewsFactory do

  def run(pieces) do
    |> Stream.chunk_every(50)
    |> Stream.flat_map(&add_thread/1)
    |> Stream.chunk_every(100)
    |> Stream.flat_map(&add_head/1)
    |> Enum.each(&output/1)

  defp add_thread(pieces) do
    Process.sleep(50), &(&1 <> "--"))

  defp add_head(pieces) do
    Process.sleep(100), &("o" <> &1))

  defp output(screw) do


Enum.chunk is the eager version of the same concept:

> Enum.chunk(1..6, 2)
[[1, 2], [3, 4], [5, 6]]

Enum.flat_map does the opposite:

> chunks = Enum.chunk(1..6, 2)
> Enum.flat_map(chunks, &(&1))
[1, 2, 3, 4, 5, 6]

Designing Your Elixir Applications

Create a new application using mix:

$ mix new dungeon_crawl
$ cd dungeon_crawl

Run the tests:

$ mix test

New mix tasks can be created under lib/mix/tasks, e.g. start.ex:

defmodule Mix.Tasks.Start do
  use Mix.Task

  def run(_), do: IO.puts "Hello, World!"

The task can be run as follows:

$ mix start
Compiling 2 files (.ex)
Generated dungeon_crawl app
Hello, World!

The task function run must be part of the Mix.Tasks module and accept a single argument.

New structs can be defined using defstruct (dungeon_crawl/lib/dungeon_crawl/character.ex):

defmodule DungeonCrawl.Character do
  defstruct name: nil,
    description: nil,
    hit_points: 0,
    max_hit_points: 0,
    attack_description: nil,
    damage_range: nil

Load all modules automatically using mix (in the project's root folder):

$ iex -S mix

A struct can be created as follows:

> warrior = %DungeonCrawl.Character{name: "Warrior"}
  attack_description: nil,
  damage_range: nil,
  description: nil,
  hit_points: 0,
  max_hit_points: 0,
  name: "Warrior"

lost me on page 129, I'm done with this book, sorry