# 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`/`break` constructs
- **Automatic Optimization**: Recognizes common patterns and optimizes to `Enum` functions
- **Mutable-like State**: Bindings carry over between iterations, simulating mutable state
- **Pattern Recognition**: Supports 26 optimization patterns out of the box
## Quick Start
```elixir
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
end
```
### Loop recognizes common patterns and rewrites them internally
```elixir
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:
```elixir
loop do
break(123) # Returns 123
end
```
### State Across Iterations
Bindings at the end of each iteration are carried to the next, creating the
illusion of mutable state:
```elixir
i = 0
loop do
IO.puts(i)
i = i + 1 # This binding carries to the next iteration
end
```
### Initial Bindings
Declare initial values using keyword arguments:
```elixir
loop i: 0, step: 2 do
IO.puts(i)
i = i + step
end
```
## Automatic Pattern Optimization
Loop recognizes 26 common patterns and automatically optimizes them to
equivalent `Enum` operations at compile-time, with zero runtime overhead.
### 1. Map
```elixir
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
```elixir
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
```elixir
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
```elixir
loop acc: [] do
if list == [], do: break(acc)
[h | list] = list
acc = [h | acc]
end
# => Enum.reverse(list)
```
### 5. Filter+Map
```elixir
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 * 10
```
### 6. Find
```elixir
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?
```elixir
loop do
if list == [], do: break(false)
[h | list] = list
if h == target, do: break(true)
end
# => Enum.member?(list, target)
```
### 8. Find Index
```elixir
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
```elixir
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
```elixir
loop count: 0 do
if list == [], do: break(count)
[_ | list] = list
count = count + 1
end
# => length(list)
```
### 11. Any
```elixir
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
```elixir
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
```elixir
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
```elixir
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
```elixir
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
```elixir
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
```elixir
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
```elixir
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
```elixir
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
```elixir
loop best: hd(list) do
list = tl(list)
if list == [], do: break(best)
best = max(best, hd(list))
end
# => Enum.max(list)
```
### 21. Min
```elixir
loop best: hd(list) do
list = tl(list)
if list == [], do: break(best)
best = min(best, hd(list))
end
# => Enum.min(list)
```
### 22. Frequencies
```elixir
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
```elixir
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
```elixir
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
```elixir
loop sum: 0 do
if list == [], do: break(sum)
sum = sum + hd(list)
list = tl(list)
end
# => Enum.sum(list)
```
### 26. Product / Reduce
```elixir
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:
```elixir
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
```elixir
loop do
IO.write(Enum.random(["░", "▒", "▓", "█"]))
Process.sleep(100)
end
```
## Important Notes
- **Scoping**: Loops don't modify surrounding scope. Capture the return value:
```elixir
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 here
```
- **Proof of Concept**: This library demonstrates that Elixir macros are
powerful enough to bridge imperative and functional paradigms. However,
idiomatic Elixir code typically uses `Enum` functions directly. I'm not
suggesting you should code with `loop`s in Elixir.
## Design Philosophy
Loop is a thought experiment and proof of concept showing that:
1. Elixir's meta-programming capabilities enable unconventional syntax
2. Macros can recognize algorithmic patterns
3. Imperative-looking code can be compiled to efficient functional operations