Recursion in Elixir
As Elixir is a purely functional language, it doesn’t have loops. Instead, it uses a wide variety of recursive functions to achieve the same or better results. Below is an example of a list and calculating it's length. This shows how recursive functions can be used to work like a loop. **Tail Call Optimization** A traditional function stack looks like this, with each member of the list becoming another function on the stack: ┌────────────────────────┐ │ length([1, 2, 3, 4], 0)│ <── Each member of the list ├────────────────────────┤ becomes a function on │ length([2, 3, 4], 1) │ the stack ├────────────────────────┤ │ length([3, 4], 2) │ ├────────────────────────┤ │ length([4], 3) │ ├────────────────────────┤ │ length([], 4) │ └────────────────────────┘ A tail call optimized function stack, like that used in Elixir, looks like this: ┌────────────────────────┐ ┌────────────────────────┐ │ length([1, 2, 3, 4], 0)│ <──── │ length([2, 3, 4], 1) │ └────────────────────────┘ ├────────────────────────┤ │ length([3, 4], 2) │ ├────────────────────────┤ │ length([4], 3) │ ├────────────────────────┤ │ length([], 4) │ └────────────────────────┘ Function calls on the stack are replaced, so there is only one function on the stack at a time. **Requirements for Tail Call Optimization** In order to be tail call optimized, a function must call itself as the last operation. This is not a proper tail call: def join([h|t]) do h <> " " <> join(t) end But this is: def join([h|t], string) do string = string <> " " <> h join(t, string) end So, now let's look at some examples of a list (MyList) and calling different recursive functions that allow us to work with the list's data. **MyList.length/1** defmodule MyList do def length(list) do length(list, 0) end defp length([], count) do count end defp length([_|t], count) do length(t, count + 1) end end **MyList.each/2** defmodule MyList do def each([], _fun) do :ok end def each([h|t], fun) do fun.(h) each(t, fun) end end Usage: MyList.each [1, 5, 12], fn(num) -> IO.puts(num) end # 1 # 5 # 12 # => :ok **MyList.map/2** defmodule MyList do def map(list, fun) do do_map(list, fun, []) end defp do_map([], _fun, acc) do :lists.reverse(acc) end defp do_map([h|t], fun, acc) do result = fun.(h) acc = [result | acc] do_map(t, fun, acc) end end The final do_map clause could also be shortened like this: defp do_map([h|t], fun, acc) do do_map(t, fun, [fun.(h) | acc]) end Usage: list = [ {"Nolan", 30}, {"Alex", 32} ] MyList.map list, fn({name, _age}) -> name end # => ["Nolan", "Alex"]
No comments yet