defmodule MyList do
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
end
IO.inspect(MyList.sum([1, 2, 3, 4]), label: "sum")
sum: 10
Elixir has no for/while loops that mutate a
counter. Repetition is recursion: a function that
calls itself on a smaller problem, with one clause
for the base case.
defmodule MyList do
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
end
IO.inspect(MyList.sum([1, 2, 3, 4]), label: "sum")
sum: 10
Pattern matching on [head | tail] and [] picks
the clause. The base case comes first so it's
obvious when recursion stops.
defmodule Countdown do
def run(0), do: IO.puts("liftoff!")
def run(n) when n > 0 do
IO.puts(n)
run(n - 1)
end
end
Countdown.run(3)
3
2
1
liftoff!
A tail call is one where the recursive call is the very last thing the function does. The BEAM reuses the stack frame, so it runs in constant stack space. The usual trick is an accumulator argument.
defmodule Fact do
def of(n), do: of(n, 1)
defp of(0, acc), do: acc
defp of(n, acc) when n > 0, do: of(n - 1, acc * n)
end
IO.inspect(Fact.of(20), label: "20!")
20!: 2432902008176640000
Integers are arbitrary precision, so this doesn't overflow; it just gets big.
IO.inspect(Fact.of(30), label: "30!")
30!: 265252859812191058636308480000000
Building a list? Prepend while recursing (cheap) and reverse once at the end (one pass).
defmodule Doubler do
def double(list), do: double(list, [])
defp double([], acc), do: Enum.reverse(acc)
defp double([h | t], acc), do: double(t, [h * 2 | acc])
end
IO.inspect(Doubler.double([1, 2, 3]), label: "doubled")
doubled: [2, 4, 6]
In everyday code you rarely write this by hand:
Enum and Stream already implement these loops.
IO.inspect(Enum.sum([1, 2, 3, 4]), label: "Enum.sum")
Enum.sum: 10
Pitfall: "always make it tail-recursive" is a myth carried over from older advice. Since OTP R12, body recursion like
MyList.sum/1is often just as fast, and for building lists it can beat the accumulator-plus-Enum.reverse/1version. Tail recursion matters for infinite loops, like a process's receive loop, which must never grow the stack.
Pitfall: forgetting the base case, or a guard like
when n > 0, gives you an infinite loop or aFunctionClauseErroron input you didn't expect (Countdown.run(-1)). Guard against bad input at the edge.