简体   繁体   中英

Infinite loop with counter in elixir

I'm studying functional programming and I want to implement something like this.

while(true) do
  if(somethingHappensHere) {
    break
  }
  counter++
end
return counter

How can I do this in functional way using elixir?

Thanks for this.

While in most functional programming languages one would use a recursion for this task, Elixir particularly provides the way to do this without using an explicit recursion call: Enum.reduce_while/3 :

Enum.reduce_while(1..100, 0, fn i, acc ->
  if condition, do: {:halt, acc}, else: {:cont, acc + i}
end)

For lazy evaluation one would use Stream.reduce_while/3 .

To make it infinite , one might use one of infinite generators, provided by Stream module, like Stream.iterate/2 :

Stream.iterate(0, &(&1+1)) |> Enum.reduce_while(0, fn i, acc ->
  if i > 6, do: {:halt, acc}, else: {:cont, acc + 1}
end)
#⇒ 7

For the sake of recursion, this is how the recursive solution might be implemented in Elixir:

defmodule M do
  def checker, do: & &1 <= 0
  def factorial(v, acc \\ 1) do
    if checker().(v), do: acc, else: factorial(v - 1, v * acc)
  end
end

M.factorial 6                            
#⇒ 720

Not sure about elixir specifically, but you can achieve this using recursion :

function myFunction(int counter)
{
  if (condition) {
    return counter
  }
  return myFunction(counter + 1)
}

This essentially sets up a function that can infinitely recurse (call itself), each time passing in the next counter value.

By having the recursive call as the last thing the function does, this is known as tail-call recursion which elixir supports (as per: Does Elixir infinite recursion ever overflow the stack? )

This can then be used as such:

int counterValue = myFunction(0)

With the function only returning once the condition is true.

You could also make this more generic by having the function take another function that returns true or false (ie performs the conditional check).

As I said, unfortunately I'm not aware of the syntax of elixir, but I'm sure you'll be able to bridge that gap.

An example of something in Elixir syntax:

defmodule SOQuestion do 
  def test(counter) do
    if (something_happens_here?()), do: counter, else: test(counter+1)
  end
  def something_happens_here?() do
    true
  end
end

And it would be invoked like this:

SOQuestion.test(0)

A couple of notes on this:

1.) It's a code fragment. Obviously it's tough to be very complete given the broad nature of your question.

2.) something_happens_here? being a predicate it would normally be named ending with a question mark.

3.) If something_happens_here? were defined in a different module, then the call would be if (Module.something_happens_here?())

4.) I've obviously coded something_happens_here? to simply return true unconditionally. In real code, of course, you'd want to pass some argument to something_happens_here? and act on that to determine which boolean to return.

Given all that I totally agree with @mudasowba--this sort of thing is usually better handled with one of the higher order functions built into the language. It's less error prone and often much easier for others to read too.

As mentioned, you could use a number of built-in functions like Enum.reduce_while/3 . However, sometimes it is just as easy (or fun) to use simple recursion.

Using recursion:

I will make some generic examples and use bar(foo) as an example of your somethingHappensHere condition.

1) If bar(foo) is something allowed in a guard clause :

defmodule Counter do
  def count do
    count(foo, 0)
  end

  defp count(foo, count) when bar(foo), do: count

  defp count(foo, count), do: count(foo, count + 1)
end

2) If bar(foo) is a function that returns a boolean:

defmodule Counter do
  def count(foo) do
    count(foo, 0)
  end

  defp count(foo, count) do
    if bar(foo) do
      count
    else
      count(foo, count + 1)
    end
  end
end

3) If bar(foo) returns something other than a boolean, that you can pattern-match on, like:

defmodule Counter do
  def count(foo) do
    count(foo, 0)
  end

  defp count(foo, count) do
    case bar(foo) do
      {:ok, baz}  -> count
      {:error, _} -> count(foo, count + 1)
    end
  end
end

Call the module and function:

Counter.count(foo)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM