Wrapping a tail recursive function with try/catch

Hi,

I’m pretty sure it is the case but I found conflicting informations online.

Can you confirm that the tail call optimization will take place for a function when only the first call is wrapped in a try/catch?

For instance this module:

defmodule T do
  def top do
    until(1_000_000_000)
  catch
    e -> {:got, e}
  end

  defp until(n, n) do
    throw(:hello)
  end

  defp until(n, x) do
    until(n, x + 1)
  end

  defp until(n) do
    until(n, 0)
  end
end

IO.inspect(T.top())

As you can see, the first call to until is wrapped in a try/catch but afterwards, there are no more wrapping blocks around the recursion. So TCO should be there, right?

I’m pretty sure this example would blow up if that were not the case anyway :smiley:

Other case, with this code:

defmodule T do
  def top do
    until(1_000)
  catch
    e -> {:got, e}
  end

  defp until(n, m) when m >= n do
    throw(:hello)
  end

  defp until(n, x) do
    add =
      try do
        some_other_fun()
      catch
        _ -> 1
      end

    until(n, x + add)
  end

  defp until(n) do
    until(n, 0)
  end

  defp some_other_fun do
    case Enum.random(1..2) do
      1 -> throw(:nope)
      2 -> 2
    end
  end
end

The try/catch around some_other_fun does not wrap the tail call, so TCO is still there right?

Thanks!

defmodule T do
  def top do
    until(1_000_000_000)
  catch
    e -> {:got, e}
  end

  defp until(n, n) do
    IO.inspect(Process.info(self(), :current_stacktrace))
    throw(:hello)
  end

  defp until(n, x) do
    until(n, x + 1)
  end

  defp until(n) do
    until(n, 0)
  end
end

IO.inspect(T.top())

I add one line to your code and get a result below

{:current_stacktrace,
 [
   {Process, :info, 2, [file: 'lib/process.ex', line: 773]},
   {T, :until, 2, [file: 'iex', line: 12]},
   {T, :top, 0, [file: 'iex', line: 6]},
   {:elixir, :"-eval_external_handler/1-fun-2-", 4,
    [file: 'src/elixir.erl', line: 309]},
   {:erl_eval, :do_apply, 7, [file: 'erl_eval.erl', line: 748]},
   {:erl_eval, :expr_list, 7, [file: 'erl_eval.erl', line: 961]},
   {:erl_eval, :expr, 6, [file: 'erl_eval.erl', line: 454]},
   {:elixir, :eval_forms, 4, [file: 'src/elixir.erl', line: 294]}
 ]}
{:got, :hello}

It looks like TCO is take place. You can try this with your second one

Ah yes, indeed, why did I not think of it :smiley:

1 Like

I’m curious what you’re doing that requires throw?

A parser and interpreter for user-defined scripts.