An imperative style loop macro implementation.
Usage:
use Loop
loop do
...
endFor further information and examples look at the help page of Loop.loop:
iex> use Loop
iex> h loopThis is a demo that macros are actually powerful enough to simulate something alien in functional programming like a loop with seemingly mutable state.
Yes, we can do it. But I'm not at all saying we should do it.
This thing is instead a proof of concept for myself. We can use imperative
style loop code, and have macros that recognize certain patterns of the loop
and translate it under the hood into a functional pattern, like an
Enum.reduce or even an Enum.sum.
Recognized Optimization Patterns
The Loop module automatically recognizes and optimizes the 26 classic patterns below, plus dozens of additional advanced variants (see the README for a showcase):
1. Map
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = [transform(h) | acc]
end
# => Enum.map(list, fn h -> transform(h) end)2. Filter
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if condition(h), do: [h | acc], else: acc
end
# => Enum.filter(list, fn h -> condition(h) end)3. Reject
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if condition(h), do: acc, else: [h | acc]
end
# => Enum.reject(list, fn h -> condition(h) end)4. Reverse
loop acc: [] do
if list == [], do: break(acc)
[h | list] = list
acc = [h | acc]
end
# => Enum.reverse(list)5. Filter+Map
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if condition(h), do: [transform(h) | acc], else: acc
end
# => for h <- list, condition(h), do: transform(h)6. Find
loop do
if list == [], do: break(nil)
[h | list] = list
if condition(h), do: break(h)
end
# => Enum.find(list, fn h -> condition(h) end)7. Member?
loop do
if list == [], do: break(false)
[h | list] = list
if h == target, do: break(true)
end
# => Enum.member?(list, target)8. Find Index
loop index: 0 do
if list == [], do: break(nil)
[h | list] = list
if condition(h), do: break(index)
index = index + 1
end
# => Enum.find_index(list, fn h -> condition(h) end)9. Count
loop count: 0 do
if list == [], do: break(count)
[h | list] = list
count = if condition(h), do: count + 1, else: count
end
# => Enum.count(list, fn h -> condition(h) end)10. Length
loop count: 0 do
if list == [], do: break(count)
[_ | list] = list
count = count + 1
end
# => length(list)11. Any
loop result: false do
if list == [], do: break(result)
[h | list] = list
result = result or condition(h)
end
# => Enum.any?(list, fn h -> condition(h) end)12. All
loop result: true do
if list == [], do: break(result)
[h | list] = list
result = result and condition(h)
end
# => Enum.all?(list, fn h -> condition(h) end)13. Each
loop do
if list == [], do: break()
[h | list] = list
side_effect(h)
end
# => Enum.each(list, fn h -> side_effect(h) end)14. Take While
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if condition(h), do: [h | acc], else: break(Enum.reverse(acc))
end
# => Enum.take_while(list, fn h -> condition(h) end)15. Drop While
loop do
if list == [], do: break([])
[h | list] = list
unless condition(h), do: break([h | list])
end
# => Enum.drop_while(list, fn h -> condition(h) end)16. With Index
loop acc: [], i: 0 do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = [{h, i} | acc]
i = i + 1
end
# => Enum.with_index(list)17. Zip
loop acc: [] do
if list1 == [] or list2 == [], do: break(Enum.reverse(acc))
[h1 | list1] = list1
[h2 | list2] = list2
acc = [{h1, h2} | acc]
end
# => Enum.zip(list1, list2)18. Reduce While
loop acc: init do
if list == [], do: break(acc)
[h | list] = list
if stop_condition(h, acc), do: break(acc)
acc = transform(h, acc)
end
# => Enum.reduce_while(list, init, fn h, acc -> ... end)19. Dedup
loop acc: [], prev: nil do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if h == prev, do: acc, else: [h | acc]
prev = h
end
# => Enum.dedup(list)20. Max
loop best: hd(list) do
list = tl(list)
if list == [], do: break(best)
best = max(best, hd(list))
end
# => Enum.max(list)21. Min
loop best: hd(list) do
list = tl(list)
if list == [], do: break(best)
best = min(best, hd(list))
end
# => Enum.min(list)22. Frequencies
loop freq: %{} do
if list == [], do: break(freq)
[h | list] = list
freq = Map.update(freq, h, 1, &(&1 + 1))
end
# => Enum.frequencies(list)23. Map.new
loop acc: %{} do
if list == [], do: break(acc)
[h | list] = list
acc = Map.put(acc, key_fn(h), val_fn(h))
end
# => Map.new(list, fn h -> {key_fn(h), val_fn(h)} end)24. Scan
loop acc: [], running: init do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
running = running + h
acc = [running | acc]
end
# => Enum.scan(list, init, &+/2)25. Sum
loop sum: 0 do
if list == [], do: break(sum)
sum = sum + hd(list)
list = tl(list)
end
# => Enum.sum(list)26. Product / Reduce
loop acc: init do
if list == [], do: break(acc)
acc = acc op hd(list)
list = tl(list)
end
# => Enum.product(list) when init=1 and op=*
# => Enum.reduce(list, init, fn x, acc -> acc op x end)
Summary
Functions
Simulates an imperative style infinite loop that can be exited with
break(value).
Functions
Simulates an imperative style infinite loop that can be exited with
break(value).
loop do
IO.write("and again ")
Process.sleep(500)
endAdd use Loop to your module in order to use Loop. It will import a loop
macro that can be used as explained below. If you want to go without the
import, simply require Loop and use Loop.loop instead of loop in the
coming examples.
This will print a random pattern to the terminal:
loop do: IO.write(Enum.random(["░", "▒", "▓", "█"]))Just like this, but this time with some colors:
items = ["▙", "▚", "▛", "▜", "▞", "▟"]
loop do
IO.write(Enum.random([IO.ANSI.red(), IO.ANSI.green(), IO.ANSI.blue()]))
IO.write(Enum.random(items))
Process.sleep(100)
IO.write(IO.ANSI.cursor_left())
endbreak() out of the loop
loop runs infinitely unless you escape from the loop with break(value).
The return value of the loop do ... end will be value. If used as
break(), nil is returned. This just returns 123:
loop do
break(123)
endSimulation of mutable state
The bindings from the end of the do ... end block are carried over to the
next execution of the block. The following example prints 0, 1, 2, and so on
without ever stopping:
i = 0
loop do
IO.puts(i)
i = i + 1
endThis is how you can stop at, let's say 100:
i = 0
loop do
IO.puts(i)
if i == 100 do
break()
end
i = i + 1
endA shorter form of the above looks like this:
loop i: 0 do
IO.puts(i)
i = if i == 100, do: break(), else: i + 1
endYou can use more than one initial value, like a steping value here:
loop i: 0, step: 2 do
IO.puts(i)
i = i + step
endloop just returns values, but does not change variables
This prints the numbers from 10 down to 1 and at the end returns the value
{:final, 1}. It also demonstrates that the loop cannot change the
surrounding environment's variables. The value of a won't be affected:
a = 10
loop do
IO.puts(a)
if a < 2, do: break({:final, a})
a = a - 1
end # => {:final, 1}
a #=> 10Quick and dirty service example
Let's spawn a counter process that listens for :inc, :dec, :get and
:stop messages:
pid = spawn_link(fn ->
loop counter: 0 do
counter =
receive do
:inc -> counter + 1
:dec -> counter - 1
{:get, from} -> send(from, counter) ; counter
:stop -> break()
end
end
end)Send it some commands:
send(pid, :inc)
send(pid, :inc)
send(pid, :inc)
send(pid, :dec)
send(pid, {:get, self()})
flush() #=> 2
send(pid, :stop)Map a function over a list - but in imperative style
This will return a list with the items doubled in the original list:
list = [1, 2, 3]
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = [2 * h | acc]
endReduce on a list:
Let's sum the elements of a list:
list = [1, 2, 3]
loop [sum: 0] do
if Enum.empty?(list), do: break(sum)
sum = sum + hd(list)
list = tl(list)
end