How To Do A For Loop In Elixir (using only recursion)

One question that came up for me when I first started with FP was how to do things that I was able to do in imperative without mutable variables. One that was tricky was trying to figure out how to do a for loop with no mutable variables. There are better ways to do this (which I am sure others will share) but this is what I’d consider the simplest way to do a for loop in Elixir via recursion:

# How to do a "for" loop in Elixir via recursion

defmodule ForLoop do

  def for_loop(count, action) when is_integer(count) and is_function(action) do
    acc = 0
    loop(action, count, acc)
  end

  defp loop(action, count, acc) do
    if acc <= count do
      action.(acc)
      loop(action, count, acc+1)
    end
  end

end

#Simply write out the numbers 0-10
ForLoop.for_loop(10,&(IO.puts(&1)))
3 Likes

Simplest way i know:

for x <- 0..10, do: IO.puts x

12 Likes

If you wanted to implement a for loop as a re-usable concept, for the sake of understanding Elixir, recursion, pattern matching etc, then sure :slight_smile: I might do things very slightly differently;

defmodule ForLoop do

  def for_loop(count, action) when is_integer(count) and is_function(action) do
    loop(action, count, 0)
  end

  defp loop(_action, count, acc) when acc > count, do: :ok
  defp loop(action, count, acc) when acc <= count do
      action.(acc)
      loop(action, count, acc+1)
  end

end

However, in real life I’d just do as @thousandsofthem suggests, or use Enum.each:

Enum.each(0..10, &(IO.puts(&1)))
9 Likes

That blog entry is irrelevant :wink: Do you really consider your solution to be simplest?

ForLoop.for_loop(10,&(IO.puts(&1)))
Enum.each(0..10, &(IO.puts(&1)))
for x <- 0..10, do: IO.puts x

Try to look at this code like had no knowledge about programming whatsoever, and answer yourself this: Which line describes what is going to happen when you run it best?

There was some passive aggressive PS. here. And it was uncalled for.

1 Like

@sztosz This is what I said:

Take extra note of the part about “via recursion”. Of course there are simpler ways to write that code. I was trying to demonstrate how to write the code with recursion specifically for developers that are new to FP.

Yes, we can tell them to use a for comprehension or an Enum.each. But the point of my post was to share a technique to write that particular construct using recursion.

Ok, but what is the gain here for programmers new to elixir or even to programming? Recursion is great, but why use it for using for_loop, especially when in your example it is unclear what argument is sent to the action in for_loop call, and also it does not allow you to pass any argument to your action. I think there are many good examples of recursion, and your unfortunately is just an example of over-complicating code.

Please share a good example of recursion so I can see how best to go back and edit my example to make it clearer.

Underwater, of course, both the for-construct as enumerables use recursion themselves, and are indeed defined similar to your @Onor.io´s program above .

What might be also interesting, is how to emulate a while-loop in Elixir (but this too something that you should not use/need in practice – there are better constructs providing similar functionality):

defmodule While do
  @doc """
  Loops `body` until a value is thrown using `throw/1`
  Passed to `body` is the `starting_value` (which defaults to `nil`) in the first iteration
  in the next iterations, the result of the previous iteration is used as passed parameter.
  """
  def loop(body, starting_value \\ nil) when is_function(body) do
    try do
      iteration_result = body.(starting_value)
      loop(body, iteration_result)
    catch
      thrown_result -> thrown_result
    end
  end

  def random_example do
    result = loop fn ->
      x = :rand.normal
      IO.inspect x
      if x > 1,do: throw x
    end
    IO.puts "Result: #{result}"
  end

  def counter_example do
    loop(fn x -> 
      if x > 100, do: throw "DONE"
      IO.puts x
      x + 1
    end, 0)
  end
end
defmodule MyList do
  def flatten([]), do: []

  def flatten([ head | tail ]) do 
    flatten(head) ++ flatten(tail)
  end

  def flatten(head), do: [ head ]
end

IO.inspect MyList.flatten([ [1], [ 2, [3] ] , [4]]) # Returns [1,2,3,4]
IO.inspect MyList.flatten([ [], [ [], [3] ] , [4]]) # Returns [3,4]

Taken from http://benjamintan.io/blog/2013/06/13/elixir-for-the-lazy-impatient-and-busy-lists-and-recursion/

defmodule Factorial do  
  def of(0), do: 1
  def of(n), do: of(n, 1)
  def of(1, acc), do: acc 
  def of(n, acc) when n > 1 do: of(n - 1, acc * n)
end

Taken from https://blog.codeship.com/statefulness-in-elixir/

Those are good examples of recursion. Re-implementing basic loops it not, especially if that loop is not a general purpose loop, and can only be used if only argument for function that loop calls is the inner loop-accumulator. When someone new to programming or Elixir will see that for_loop, he can be tempted to use it, and be confused when simple loop will start to throw errors.

1 Like

No question there are better approaches. I had a novice ask me at a meetup about how to code something with recursion and I was able to answer him but I told him that I almost never use recursion in practice because there are better approaches (like Enum.each and the for comprehension). I was simply trying to provide a small, trivial example of how to emulate a for loop from an imperative language in Elixir.

Those are both excellent examples of recursion but considering that I was trying to demonstrate how one could simulate a for loop from an imperative language (something analogous to

for(i=0; i <=10; i++)
{
action();
}

those would not have been the examples I would have picked. I said right from the start that I was solely trying to demonstrate how to do something analogous to an imperative for loop without mutable values.

Ok i think the issue is that you should not do that at all. You should not simulate imperative or object oriented (with mutable state) constructs in functional languages. Your example is still not good.
for x <- 0..10, do: action(x)
is what you probably should have used.

Anyway that old plain C-like for loop with starting value accumulator is something that is not really readable, and has no use in modern languages.
In ruby you have:

some_objects.each {  |an_object| action(object) }
# or
for an_object in some_objects
  action(an_object)  
end

In python you also iterate over collection like this

for an_object in some_objects:
    action(an_object)

Even in Java since Java 5 you would probably do

for(ClassName an_object : some_objects)  {
   action(an_object)
}

One of my most hated languages: JavaScript, has similar constructs

function callback_function(currentObject, index, array) {
  action(currentObject)
}
some_objects.forEach(callback_function)

Why? Because all of those are much more readable and descriptive, then that old plain C-like for loop. Answer yourself, how often you really need that index? Don’t you really want the object that stands behind that index?

And in Elixir you have what @thousandsofthem and @jwarlander wrote. And you should be using it instead of trying to wrap some logic into totally (in Elixir) out of place C-like for loop :slight_smile:

2 Likes

I’m jumping in late here, but I worry this thread is going off in the weeds. If one is helping to introduce an imperative programmer into Elixir and they ask “how do you handle for loops?” is it more helpful to show them that recursion can do everything imperative iteration can do, or jump immediately to higher order functions or for comprehensions? I see value in showing the recursive solution, but quickly moving over it into better abstractions. IMHO, if a functional programmer has not, at some point, implemented map and reduce with recursion, then they don’t really understand FP.

7 Likes

I’ve never implemented map and reduce and I don’t feel like I’m a worse functional developer because of that? (maybe I did implement them in Haskell classes a few years ago). Why do you say that about map and reduce specifically?

1 Like

Maybe I spoke too hastily. map can be implemented on top of reduce. reduce can be implemented on top of recursion. I’m drawing a blank right now for what recursion can do that reduce cannot. I suppose my core point is that reduce is a level above recursion. If you can implement reduce from recursion, then you’ve got a solid handle on the core of FP. Other higher order functions can be derived from reduce.

1 Like

All these talk about fold reminds me of a paper that I’ve yet to read: A tutorial on the universality and
expressiveness of fold

2 Likes

Hm! Interesting paper. Very cool to see that so much can be built on top of foldr / foldl.

One thing the paper does not state, (and is often overlooked), is that foldl is tail-recursive, while foldr is not. There is a great example on the Haskell wiki about why this is the case.


EDIT: It seems that the distinction between tail-call and non-tail-call functions in Erlang/Elixir is not that important.

See:

1 Like

There is nice book https://www.manning.com/books/functional-programming-in-scala .There a a lot common concepts and more like what is pure function . The drawback is the code is in Scala :slight_smile:

I also think there is one benefit for basically everyone of implementing various types of loops explicitly using recursion, and that is to understand what is really going on. So while you might most of the time use libraries or provided constructs instead of actually explicitly doing yourself, really knowing what is going will make you a better programmer. It will allow you use libraries and provided constructs in a better way.

Robert

11 Likes

Wise words from Programming Elixir by Dave Thomas

L. Peter Deutsch once penned, “To iterate is human, to recurse divine.” And
that’s certainly the way I felt when I first started coding Elixir. The joy of
pattern-matching lists in sets of recursive functions drove my designs. After
a while, I realized that perhaps I was taking this too far.
In reality, most of our day-to-day work is better handled using the various
enumerators built into Elixir. They make your code smaller, easier to understand,
and probably more efficient.
Part of the process of learning to be effective in Elixir is working out for
yourself when to use recursion and when to use enumerators. I recommend
enumerating when you can.

4 Likes