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

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
end

Loop 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
end

State 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
end

Initial Bindings

Declare initial values using keyword arguments:

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

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 * 10

6. 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)
end

Important Notes

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