Loops implemented in Elixir
Stem from a discussion in the ElixirForum about how to create a loop in a functional language like Elixir, only using recursion. This library it is more of an experiment than a library in itself.
Loop.Basic.loop/4 answers the original question .
Loop.Sequential.loop/4 is a bit more elaborate, allowing to use functions with more arities,
allow users to use no arguments, or two (term, and accumulator), in addition to the only one
argument allowed in Loop.Basic.loop/4.
Loop.Concurrent.loop/4 takes it even a step further, making use of Elixir concurrency model.
This function dispatches fun concurrently.
The basic usage of Loop.Sequential.loop/4 and Loop.Concurrent.loop/4 is
loop(term, condition, transform, fun)
-
termis any value that will be first passed to the loop. -
conditionwill be evaluated at every new iteration and determined if should continue or stop. -
transformwill transformtermat every iteration, and pass the transformed value to the next recursive loop call. -
funis what will be executed in every iteration. Results of each iteration are returned in a list. If you care about the the order that this function is going to be executed, useLoop.Sequential. If all you care is about speed and the final result, then useLoop.Concurrent.In
Loop.Sequential.loop/4, all three functions (condition,transformandfun) accept 0, 1 and 2 arguments; being first argumenttermand second the accumulator stored from previous iterations (starting with an empty list ([])).In
Loop.Concurrent.loop/4, all three functions (condition,transformandfun) accept 1 or no arguments; beingtermthe argument used with arity 1.
iex> Loop.Sequential.loop(1, &(&1 <= 5))
[1, 2, 3, 4, 5]
# from 2.0 to -2.0, decreasing it by 0.5
iex> Loop.Sequential.loop(2.0, &(&1 >= -2.0), &(&1 - 0.5))
[2.0, 1.5, 1.0, 0.5, 0.0, -0.5, -1.0, -1.5, -2.0]
iex> Loop.Sequential.loop(12345, &(String.length("#{&1}") <= 10), &(&1 * 7), &(:"#{&1}"))
[:"12345", :"86415", :"604905", :"4234335", :"29640345", :"207482415", :"1452376905"]
iex> Loop.Concurrent.loop(1, &(&1 <= 5))
[1, 2, 3, 4, 5]
Loop.Sequential.loop/4: lines are printed in the right order but the execution time is long
{mtime, result} = :timer.tc(fn ->
Loop.Sequential.loop(1, &(&1 <= 10), &(&1 + 1),
fn(x) ->
square = x * x
# a resource intensive process that will take time
:timer.sleep(2000)
IO.write("#{square} ")
square
end
)|> IO.inspect
end)
#=> 1 4 9 16 25 36 49 64 81 100
IO.puts "time elapsed (sequential): #{Float.ceil(mtime / 1000000, 2)} seconds"
#=> time elapsed (sequential): 20.02 seconds
Loop.Concurrent.loop/4: lines are printed in the right order, but the result is returned in the
right order
{mtime, _} = :timer.tc(fn ->
Loop.Concurrent.loop(1, &(&1 <= 10), &(&1 + 1),
fn(x) ->
square = x * x
# a resource intensive process that will take time
:timer.sleep(2000)
IO.write("#{square} ")
square
end
)|> IO.inspect
end)
IO.puts "time elapsed (concurrent): #{Float.ceil(mtime / 1000000, 2)} seconds"
#=> 1 4 9 16 25 36 49 64 81 100
#=> time elapsed (concurrent): 2.01 seconds
Note that the printed results from the concurrent version are not in the right order.
If available in Hex, the package can be installed as:
-
Add loop to your list of dependencies in
mix.exs:def deps do [{:loop, "~> 0.0.1"}] end
-
Ensure loop is started before your application:
def application do [applications: [:loop]] end