elixir

Nested function definitions


I'm just working through Programming Elixir and implementing Split.

My code as is follows -

defmodule MyEnum do
    def split(l, n) do
        def split_helper([], pre, _n), do: {pre, []}
        def split_helper(l, pre, 0), do: {pre, l}
        def split_helper([h|t], pre, n), do: split_helper(t, [h|pre], n-1)
        split_helper(l, [], n)
    end
end

and I am getting the error - cannot invoke def/2 inside function/macro

Which I take to mean I can't nest named functions. As you can't write recursive anonymous functions and there is no letrec, I am wondering how to nest functions where the nested function is recursive. This is the way I would structure my code in racket as I would never need to use split_helper anywhere else.


Solution

  • Functions in Elixir cannot be nested. Period.

    The idiomatic way to implement this would be to have a private function called do_split/3.

    defmodule MyEnum do
      def split(l, n), do: do_split(l, [], n)
    
      defp do_split([], pre, _n), do: {pre, []}
      defp do_split(l, pre, 0), do: {pre, l}
      defp do_split([h|t], pre, n), do: do_split(t, [h|pre], n-1)
    end
    

    Another way to approach the problem would be to simply have several clauses of split/3 itself.

    defmodule MyEnum do
      def split(l, acc \\ [], n) # default values
    
      def split([], pre, _n), do: {pre, []}
      def split(l, pre, 0), do: {pre, l}
      def split([h|t], pre, n), do: split(t, [h|pre], n-1)
    end
    

    The least elegant, but still working approach would be to create anonymous function and pass it through.

    defmodule MyEnum do
      def split(l, n) do
        split_helper = fn
          [], pre, _, _ -> {pre, []}
          l, pre, 0, _ ->  {pre, l}
          [h|t], pre, n, split_helper ->
            split_helper.(t, [h|pre], n-1, split_helper)
        end
        split_helper.(l, [], n, split_helper)
      end
    end