published on in elixir
tags: elixir recursion

Recursion in elixir

I am new to elixir, and I find this interesting at the time of writing, I am coming from NodeJs :)

The major ingredients of recursion are:

  • An exercise to perform repeatedly,
  • A condition to break off the repeated exercise

Lets say we want to write a function that will return building identifiers where restaurants have not yet closed. The first argument is the current hour as integer, the second argument is the current minute as integer, the third argument is the list of restaurants containing; the name of the canteen, building identifier, closing hour, closing minute.

mix new restaurant
# ~/restaurant/lib/restaurant.ex

defmodule Restaurant do
  defstruct name: nil, id: nil, closing_hour: nil, closing_minute: nil

	def get_opened(hour, minute, restaurants) do
		check_if_opened(hour, minute, restaurants, [])
	end

	defp check_if_opened(hour, minute, [restaurant = %Restaurant{closing_hour: c_hour, closing_minute: c_minute} | tail], accumulator)
    do
      case (c_hour >= hour) do
        true when (c_hour != hour) -> check_if_opened(hour, minute, tail, (accumulator ++ [restaurant]))
        true when (c_hour == hour) -> fn  ->
            case (minute < c_minute) do
              true -> check_if_opened(hour, minute, tail, (accumulator ++ [restaurant]))
              false -> check_if_opened(hour, minute, tail, accumulator)
            end
          end.()
        false -> check_if_opened(hour, minute, tail, accumulator)
      end
  end
	defp check_if_opened(_, _, [], acummulator) do
		acummulator
  end
end

Explanation

get_opened\3 is the public function that is called, and this is given the parameters described earlier, the hour, minute and the list of restaurants we want to check recursively using the check_if_opened\4 function. There are two check_if_opened\4 functions, thanks to the magic of elixir, this is pattern matching at work. When check_if_opened\4 is called it will only match one based on the parameters defined on the function.

In our case we have check_if_opend\4 which is targeting an empty restaurant list and another one which will match a non empty list. Which is our golden plan of breaking off from the recursion.

This comes to mind when looking at this solution “An Enum method could have been used to operate on the restaurant list”, yes, this is possible but the objective of this snippet is to demonstrate the explicit filtering of the restaurants in order to showcase recursion in elixir.

Let’s test our restaurant module:

#~/restaurant/test/restaurant_test.exs
defmodule RestaurantTest do
  use ExUnit.Case

  setup_all do
    restaurants_list = [
      %Restaurant{name: "Kohvic center", id: "KHV", closing_hour: 16, closing_minute: 30},
      %Restaurant{name: "Kohvic center2", id: "KHV2", closing_hour: 14, closing_minute: 00},
      %Restaurant{name: "Liivi 2 Restaurant", id: "Liivi-2", closing_hour: 19, closing_minute: 45},
    ]
    {:ok, [restaurants: restaurants_list]}
  end

  test "check available restaurants for 16:46", %{restaurants: restaurants} do
    assert [%Restaurant{id: "Liivi-2"}] = Restaurant.get_opened(16, 46, restaurants) #One restaturant should be opened
  end

  test "check available restaurants for 18:46", %{restaurants: restaurants} do
    assert 0 = length(Restaurant.get_opened(19, 46, restaurants)) #All restaurants should be closed for this time
  end

  test "check available restaurants for 13:00", %{restaurants: restaurants} do
    assert 3 = length(Restaurant.get_opened(13, 00, restaurants)) #All restaurants should be opened by this time
  end