lib/fex/lazy.ex

defmodule Fex.Lazy do
  @moduledoc """
  Documentation for `Fex`.

  Lazy module won't execute the code. It will instead wrap it inside
  function and return nested function. This make the module "pure" as
  it's actually not executing any of its code

  Example:
  ```
  def allowed?(email, read_fn \\ &File.read/1) do
    Lazy.task(fn () -> read_fn.("list.json") end)
    |> Lazy.chain(&Jason.decode/1)
    |> Lazy.apply(&check_email(&1, email))
    |> Lazy.fold(& &1, fn error ->
      Logger.error("User service error occurred")
      IO.inspect(error)
      false
    end)
  end

  Calling the above function results in a function returned. None of
  the code is executed. You can "trigger" the execution by just calling
  the resulted function:

  ```
  iex(1)> function = allowed?("test@example.com")
  iex(2)> function.()
  true
  ```
  """

  def task(function) do
    fn () ->
      function.()
    end
  end

  def chain(acc, function) do
    fn () ->
      case acc.() do
        {:ok, data} -> function.(data)
        {:error, error} -> {:error, error}
      end
    end
  end

  def apply(acc, function) do
    fn () ->
      case acc.() do
        {:ok, data} -> {:ok, function.(data)}
        {:error, error} -> {:error, error}
      end
    end
  end

  def map(acc, iteration_fn) do
    fn () ->
      case acc.() do
        {:ok, data} -> Enum.map(data, iteration_fn)
        {:error, error} -> {:error, error}
      end
    end
  end

  def fold(acc, success_fn, error_fn) do
    fn () ->
      case acc.() do
        {:ok, data} -> success_fn.(data)
        {:error, error} -> error_fn.(error)
      end
    end
  end
end