Anonymous recursive functions, getting error: warning: variable "fibonaci" does not exist

Hello,

I am testing below code

fibonaci = fn
  0 -> 0
  1 -> 1
   n -> fibonaci.(n-1) + fibonaic.(n-2)
end

IO.puts fibonaci.(10)

I am getting error as

elixir fibonaci.ex
warning: variable "fibonaci" does not exist and is being expanded to "fibonaci()", please use parentheses to remove the ambiguity or change the variable name
  fibonaci.ex:4

** (CompileError) fibonaci.ex:4: undefined function fibonaci/0 (there is no such import)

Please help, Thank you.

Elixir doesn‘t have recursive anonymous functions.

1 Like

The best thing you can do, is to use 2 variables:

fib_ = fn
  0, f -> 0
  1, f -> 1
  n, f -> f.(n-1) + f.(n-2)
end

fib = fn(n) -> fib_(n, fib_) end

IO.puts fib.(10)

Please beware the autocorrection.

4 Likes

Or you can use the Y-combinator! :slight_smile:

Seriously, I suggest to find a different implementation, even though I thing learning the Y Combinator has its importance.

I wanted to be able to do this too, since sometimes I don’t want a whole module just to test a function. I found somewhere someone showed to do it like this:

fib = fn
  1, _ -> 1
  2, _ -> 1
  n, recur -> recur.(n - 1, recur) + recur.(n - 2, recur)
end

fib.(10, fib) |> IO.puts()

Put recur (or whatever stand-in name you want) within the function, to stand for the name of the function. Then when you call the function, pass the name to itself.

======================
Notes:
(I’m trying to take some notes as I go about learning, so just putting this here so I can find it) This ergonomic issue seems relevant to individuals who arrive at Elixir through the path of Livebooks, since it suggests the use case of software development over interactive computing. Building up functionality bit by bit with little functions would be the natural path in an exploratory environment. So having to reach for a module this early on, or otherwise having to understand this slightly arcane syntax, might put off people who are looking for simple, straightforward things to be simple and straightforward, or are translating from another language.

  • Present as-is, without the need to explain y combinator
  • Livebook having an optional module wrapper might allow the style of writing plain functions
  • Rosetta code-style translator
2 Likes

If you feel adventurous then you can check out my small macro that I have created for this purpose:

defmodule Foo do
  defmacro fn_rec(do: body) do
    {name, argc, body} =
      body
      |> Enum.map(fn
        {:'->', e1, [[{:when, e2, [{name, _, args}, guard]}], body]} ->
          {name, length(args), {:'->', e1, [[{:when, e2, args ++ [guard]}], body]}}

        {:'->', e1, [[{name, _, args}], body]} ->
          {name, length(args), {:'->', e1, [args, body]}}
      end)
      |> Enum.reduce(fn {name, argc, body}, {name, argc, acc} ->
        {name, argc, List.wrap(acc) ++ [body]}
      end)

    args = Macro.generate_arguments(argc, __CALLER__.module)
    func = {:fn, [], body}
    name = {name, [], Elixir}

    quote do
      tmp =
        fn f ->
          var!(unquote(name)) = fn unquote_splicing(args) -> f.(f).(unquote_splicing(args)) end
          unquote(func)
        end

      tmp.(tmp)
    end
  end

  def run do
    ack =
      fn_rec do
        a(0, n) -> n + 1
        a(m, 0) -> a.(m - 1, 1)
        a(m, n) -> a.(m - 1, a.(m, n - 1))
      end

    ack.(3, 4)
  end
end

IO.puts Foo.run
4 Likes