Loop
An Elixir macro that provides imperative-style loop syntax with automatic
compile-time optimization to functional patterns. Write loops like you would in
imperative languages, and let the compiler intelligently transform them to
efficient Enum operations.
Features
- Imperative Loop Syntax: Write familiar
loop/breakconstructs - Automatic Optimization: Recognizes common patterns and optimizes to
Enumfunctions - Mutable-like State: Bindings carry over between iterations, simulating mutable state
- Pattern Recognition: Supports 26 optimization patterns out of the box
Quick Start
use Loop
# Basic infinite loop (broken with `break/0` or `break/1`)
loop do
IO.puts("repeating...")
Process.sleep(500)
end
# Loop with state
i = 0
loop do
IO.puts(i)
i = i + 1
if i == 10, do: break()
end
# Using initial binding
loop count: 0 do
IO.puts(count)
if count >= 5, do: break(count)
count = count + 1
endLoop recognizes common patterns and rewrites them internally
quote do
loop product: 1 do
if list == [], do: break(product)
product = product * hd(list)
list = tl(list)
end
end
|> Macro.expand(__ENV__)
|> Macro.to_string()
#=> "Enum.product(list)"Core Concepts
Breaking Out of Loops
Use break() to exit a loop with nil, or break(value) to exit with a
specific value:
loop do
break(123) # Returns 123
endState Across Iterations
Bindings at the end of each iteration are carried to the next, creating the illusion of mutable state:
i = 0
loop do
IO.puts(i)
i = i + 1 # This binding carries to the next iteration
endInitial Bindings
Declare initial values using keyword arguments:
loop i: 0, step: 2 do
IO.puts(i)
i = i + step
endAutomatic Pattern Optimization
Loop recognizes 26 common patterns and automatically optimizes them to
equivalent Enum operations at compile-time, with zero runtime overhead.
1. Map
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = [h * h | acc]
end
# => Enum.map(list, fn h -> h * h end)2. Filter
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if rem(h, 2) == 0, do: [h | acc], else: acc
end
# => Enum.filter(list, fn h -> rem(h, 2) == 0 end)3. Reject
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if rem(h, 2) == 0, do: acc, else: [h | acc]
end
# => Enum.reject(list, fn h -> rem(h, 2) == 0 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 rem(h, 2) == 0, do: [h * 10 | acc], else: acc
end
# => for h <- list, rem(h, 2) == 0, do: h * 106. Find
loop do
if list == [], do: break(nil)
[h | list] = list
if String.starts_with?(h, "c"), do: break(h)
end
# => Enum.find(list, fn h -> String.starts_with?(h, "c") 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 rem(h, 2) == 0, do: break(index)
index = index + 1
end
# => Enum.find_index(list, fn h -> rem(h, 2) == 0 end)9. Count
loop count: 0 do
if list == [], do: break(count)
[h | list] = list
count = if h > 5, do: count + 1, else: count
end
# => Enum.count(list, fn h -> h > 5 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 rem(h, 2) == 0
end
# => Enum.any?(list, fn h -> rem(h, 2) == 0 end)12. All
loop result: true do
if list == [], do: break(result)
[h | list] = list
result = result and rem(h, 2) == 0
end
# => Enum.all?(list, fn h -> rem(h, 2) == 0 end)13. Each
loop do
if list == [], do: break()
[h | list] = list
IO.puts(h)
end
# => Enum.each(list, fn h -> IO.puts(h) end)14. Take While
loop acc: [] do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
acc = if h > 0, do: [h | acc], else: break(Enum.reverse(acc))
end
# => Enum.take_while(list, fn h -> h > 0 end)15. Drop While
loop do
if list == [], do: break([])
[h | list] = list
unless h < 3, do: break([h | list])
end
# => Enum.drop_while(list, fn h -> h < 3 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: 0 do
if list == [], do: break(acc)
[h | list] = list
if acc + h > 6, do: break(acc)
acc = acc + h
end
# => Enum.reduce_while(list, 0, 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, elem(h, 0), elem(h, 1))
end
# => Map.new(list, fn h -> {elem(h, 0), elem(h, 1)} end)24. Scan
loop acc: [], running: 0 do
if Enum.empty?(list), do: break(Enum.reverse(acc))
[h | list] = list
running = running + h
acc = [running | acc]
end
# => Enum.scan(list, 0, fn x, running -> running + x end)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 product: 1 do
if list == [], do: break(product)
product = product * hd(list)
list = tl(list)
end
# => Enum.product(list)
# (Other init/op combos become Enum.reduce)Practical Examples
Counter Service
Spawn a counter process with a message loop:
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(pid, :inc)
send(pid, :inc)
send(pid, {:get, self()})
flush() # => 2
send(pid, :stop)Random Pattern Animation
loop do
IO.write(Enum.random(["░", "▒", "▓", "█"]))
Process.sleep(100)
endImportant Notes
Scoping: Loops don't modify surrounding scope. Capture the return value:
a = 10 result = loop do a = a - 1 # doesn't affect outer a if a < 2, do: break({:final, a}) end # a is still 10 hereProof of Concept: This library demonstrates that Elixir macros are powerful enough to bridge imperative and functional paradigms. However, idiomatic Elixir code typically uses
Enumfunctions directly. I'm not suggesting you should code withloops in Elixir.
Design Philosophy
Loop is a thought experiment and proof of concept showing that:
- Elixir's meta-programming capabilities enable unconventional syntax
- Macros can recognize algorithmic patterns
- Imperative-looking code can be compiled to efficient functional operations