Is it possible to define original `Enum.xxx` function?

Hello. I often use this function in my development.

  def creep(match_list, f) do
    Enum.each(match_list, fn x ->
      case x do
        x when is_list(x) -> creep(x, f)
        x -> f.(x)
      end
    end)
  end

In my phoenix project, I want to include the function to Enum module.
Are there any ways to do it? Or I should not include it to Enum?
Thank you.

You cannot extend an existing module. You can redefine a module but then it will lose all original functions. So I don’t suggest it, instead you should e.g. write your own MyProject.EnumUtils module.

3 Likes

Thank you!

I’m still learning so hope this is not too naive but when would you usually use this function?

I can see it as a good way to do individual side effects inside a pipeline while keeping the original list intact, paired with the new Kernel.tap/2.

[1, 2, [3, 4]]
|> tap(fn nested_list -> creep(nested_list, &IO.inspect/1) end)
|> something_else_with_nested_list()
1 Like

Maybe this function is not necessary, as an alternative implementation is quite short :slight_smile:

match_list |> List.flatten() |> Enum.each(f)
1 Like

Agreed with small simple lists.

The strength could be to compose a number of functions that need to run on individual items without having to iterate through the list twice, first to flatten then enumerate. Something to have in the pocket for performance issues?

1 Like

It can be written in a way that do not increase the size of stack:

def flat_each([elem | rest], cb) do: do_flat_each(elem, rest, cb)

defp do_flat_each([], [next | rest], cb), do: do_flat_each(next, rest, cb)

defp do_flat_each([x | xs], cont, cb) do
  cb.(x)
  do_flat_each(xs, cont, cb)
end

defp do_flat_each(last, [], cb), do: cb.(last)

defp do_flat_each(elem, [next | rest], cb) do
  cb.(elem)
  do_flat_each(next, rest, cb)
end

It should work and is using TCO correctly. I haven’t tested it though, so it may need some love on the edge cases.

3 Likes

That looks more complex to my beginner eyes but I’ll have to look into why the original function increases the stack size. I though erlang would optimize passing the fragments of nested lists around without copying, or is it something to do with tail recursion? Also not sure what you mean by TCO, but this is looking like a topic for another thread.

I appreciate your input and taking the time to educate.

1 Like

Passing lists around and TCO are different things. TCO stands for “Tail Call Optimization”, and roughly happens when your recursive call is the last expression in your function. This way, the runtime does not need to keep the stack around until your recursion ends but discards the stack after each call. This way, your recursion is not limited by available memory regarding stack (your accumulator may grow if you don’t care).

Read more here

6 Likes

Thank you all! :grin:

1 Like