Lazy is an Erlang library providing lazy sequences and generators for memory-efficient processing of large or infinite data streams.
Erlang is inherently eager—it materializes all terms, including lists, into memory immediately. Lazy enables lazy evaluation: sequences that generate values only when accessed, making it possible to work with:
- Large datasets without loading everything into RAM
- Infinite sequences for mathematical or algorithmic exploration
- Efficient stream processing with map, filter, and other functional operations
✅ Lazy Generators — Create sequences that compute values on-demand instead of upfront
✅ Rich Function Library — Map, filter, fold, zip, scan, and 20+ generator combinators
✅ Infinite Sequences — Define and work with unbounded streams (repeat, iterate, cycle)
✅ Memory Efficient — Process multi-gigabyte datasets without loading into memory
✅ Composable — Chain generators together for complex data transformations
✅ Pure Functional — No side effects, idiomatic Erlang patterns
- Large file processing — Stream files without loading entirely into memory
- Infinite sequences — Fibonacci, mathematical sequences, periodic timers
- Data transformation pipelines — Map/filter/fold chains for ETL workflows
- Real-time data streams — Parse and process unbounded data sources
- Memory-constrained environments — Embedded systems with limited RAM
- Exploratory analysis — Quickly iterate over generated sequences
Add to rebar.config:
{deps, [
{lazy, {git, "https://github.com/goatrllr/lazy.git", {branch, "main"}}}
]}.Create a generator and materialize it into a list:
1> Gen = lazy:seq(1, 5).
#Fun<lazy.43.73700886>
2> lazy:to_list(Gen).
[1, 2, 3, 4, 5]Map over a sequence:
1> Gen0 = lazy:seq(1, 3).
#Fun<lazy.43.73700886>
2> Gen1 = lazy:map(fun (X) -> X * 2 end, Gen0).
#Fun<lazy.21.73700886>
3> lazy:to_list(Gen1).
[2, 4, 6]Filter generators:
1> Gen0 = lazy:seq(1, 10).
#Fun<lazy.43.73700886>
2> Gen1 = lazy:filter(fun (X) -> X rem 2 =:= 0 end, Gen0).
#Fun<lazy.16.73700886>
3> lazy:to_list(Gen1).
[2, 4, 6, 8, 10]Generate sequences with unfold:
Gen = lazy:unfold(fun (0) -> empty; (N) -> {N, N div 2} end, 256).
lazy:to_list(Gen). % [256, 128, 64, 32, 16, 8, 4, 2, 1]Infinite sequences with repeat or iterate:
Ones = lazy:repeat(1).
Powers = lazy:iterate(fun (X) -> X * 2 end, 1).
lazy:to_list(lazy:take(5, Powers)). % [1, 2, 4, 8, 16]Combine generators with zip/append:
Gen0 = lazy:seq(1, 3).
Gen1 = lazy:seq(4, 6).
G = lazy:zipwith(fun (A, B) -> A + B end, Gen0, Gen1).
lazy:to_list(G). % [5, 7, 9]Process streams without materializing:
Gen = lazy:seq(1, 1000000).
lazy:foreach(fun (X) -> process_item(X) end, Gen).- API_Documentation.md — Complete function reference, types, and modules
- Developer_Reference.md — Design rationale, architecture, testing, extensions
- Examples_&_Use_Cases.md — Key patterns and real-world code examples
append/1,2— Concatenate multiple generatorscycle/1— Repeat a list infinitelyempty/0— Empty generatorfrom_list/1— Wrap a list as a generatoriterate/2— Generate by repeatedly applying a functiononce/1— Single-value generatorrepeat/1— Infinite repetition of a valuerepeatedly/1— Call a function repeatedlyseq/2,3— Generate integer sequenceunfold/2— Generate from accumulator and function
apply/2— Apply function and return generatordrop/2— Skip first N valuesdropwhile/2— Skip values while predicate is truefilter/2— Keep values matching predicatefiltermap/2— Filter and map in one stepmap/2— Transform each valuereverse/1— Reverse finite sequencescan/3— Like foldl but return intermediate valuestake/2— Limit to first N valuestakewhile/2— Take values while predicate is true
unzip/1— Unzip tuples into separate generatorszip/2— Combine two generatorszipwith/2,3— Combine with a function
all/2— Test if all values match predicateany/2— Test if any value matches predicatefoldl/3— Fold left over sequencefoldr/3— Fold right over sequenceflush/1— Execute generator for side effectsforeach/2— Apply function for side effectslength/1— Count values in sequenceto_list/1— Materialize to list
next/1— Get next value and remainder generator
| Feature | Lazy | Erlang Lists | Elixir Streams |
|---|---|---|---|
| Lazy evaluation | ✅ | ❌ | ✅ |
| Infinite sequences | ✅ | ❌ | ✅ |
| Pure Erlang | ✅ | ✅ | ❌ |
| Memory efficient | ✅ | ❌ | ✅ |
| Familiar functions | ✅ | ✅ | ✅ |
| Composable pipelines | ✅ | ✅ |
Comprehensive test coverage including:
- Unit tests — Core generator and materializer operations
- Edge cases — Empty sequences, infinite sequences, error conditions
- Custom generators — User-defined generator patterns
Run tests:
make tests # All tests
make eunit # Unit tests onlyInfinite sequences with predicates — Using filter, filtermap, or dropwhile on infinite sequences can hang if the predicate never succeeds (or never fails for dropwhile):
%% This will hang forever:
Gen = lazy:filter(fun (X) -> is_atom(X) end, lazy:seq(1, infinity)).
lazy:next(Gen). % Hangs - predicate never succeedsFinite vs. Infinite — Many functions like to_list/1 and reverse/1 only work with finite sequences. Using them on infinite sequences will hang or consume unbounded memory.
Q: How do I create custom generators?
A: Write a zero-arity function that returns either empty or a tuple {Value, NextGenerator}. See Examples_&_Use_Cases.md.
Q: What if I use to_list/1 on an infinite sequence?
A: It will hang forever trying to materialize all values. Always use take/2 to limit infinite sequences before materializing.
Q: Can I use lazy with streams from files or sockets?
A: Yes! Create a custom generator that reads chunks on-demand from your source. See Developer_Reference.md.
Q: What's the performance impact?
A: Lazy generators add minimal overhead. Each next/1 call invokes a function and pattern matches. For large datasets, the memory savings typically outweigh the function call cost.
Unlicensed (check included LICENSE file for details)
Contributions welcome! Please:
- Write tests for new functionality
- Follow idiomatic Erlang style
- Update documentation for public functions
Lazy Authors:
- Maria Scott (Maria-12648430)
- Jan Uhlig (juhlig)
Documentation Author: goatrllr (https://github.com/goatrllr)