@typesugar/fusion
v0.1.0
Published
Expression templates and loop fusion — zero-cost iterator and array operations
Readme
@typesugar/fusion
Expression templates and loop fusion for TypeScript. Iterator chains like .filter().map().reduce() execute in a single pass with no intermediate arrays.
Inspired by Blitz++/Eigen expression templates and Rust's zero-cost iterator adapters.
The Problem
Standard array method chains allocate intermediate arrays at every step:
// 3 passes over the data, 2 intermediate arrays allocated
const result = users
.filter(u => u.active) // pass 1 → intermediate array
.map(u => u.score * 2) // pass 2 → intermediate array
.reduce((a, b) => a + b, 0); // pass 3The Solution
lazy() collects operations into a pipeline and fuses them into a single pass:
import { lazy } from "@typesugar/fusion";
// 1 pass, 0 intermediate arrays
const result = lazy(users)
.filter(u => u.active)
.map(u => u.score * 2)
.reduce((a, b) => a + b, 0);API
lazy(source) — Lazy Iterator Pipeline
Wraps any Iterable (arrays, Sets, Maps, generators) and returns a LazyPipeline.
Intermediate operations (chainable)
| Method | Description |
| --- | --- |
| .map(f) | Transform each element |
| .filter(pred) | Keep elements matching predicate |
| .flatMap(f) | Map to iterable and flatten |
| .take(n) | Take first N elements |
| .drop(n) | Skip first N elements |
| .takeWhile(pred) | Take while predicate holds |
| .dropWhile(pred) | Skip while predicate holds |
Terminal operations (execute the pipeline)
| Method | Returns | Description |
| --- | --- | --- |
| .toArray() | T[] | Collect into array |
| .reduce(f, init) | Acc | Fold left |
| .find(pred) | T \| null | First match |
| .some(pred) | boolean | Any match? |
| .every(pred) | boolean | All match? |
| .count() | number | Count elements |
| .forEach(f) | void | Side effect per element |
| .first() | T \| null | First element |
| .last() | T \| null | Last element |
| .sum() | number | Sum (number pipelines) |
| .min(cmp?) | T \| null | Minimum element |
| .max(cmp?) | T \| null | Maximum element |
| .join(sep?) | string | Join strings |
| .toMap(keyFn, valFn) | Map<K,V> | Collect into Map |
| .groupBy(keyFn) | Map<K,T[]> | Group by key |
Source Factories
import { range, iterate, repeat, generate } from "@typesugar/fusion";
range(0, 10) // [0, 1, 2, ..., 9]
range(0, 10, 2) // [0, 2, 4, 6, 8]
iterate(1, x => x * 2).take(5) // [1, 2, 4, 8, 16]
repeat("x").take(3) // ["x", "x", "x"]
generate(Math.random).take(4) // [0.12, 0.87, 0.34, 0.56]vec() — Element-wise Vector Operations
For numeric array operations (think NumPy-lite):
import { vec, add, sub, mul, scale, dot, magnitude, normalize } from "@typesugar/fusion";
const a = vec([1, 2, 3]);
const b = vec([4, 5, 6]);
add(a, b) // vec([5, 7, 9])
scale(a, 10) // vec([10, 20, 30])
dot(a, b) // 32
magnitude(a) // 3.741...
normalize(a) // unit vector in same directionPerformance: Single-Pass vs Multi-Pass
Operation: .filter().map().take(100) on 100,000 elements
Multi-pass (Array methods):
Pass 1: filter → allocates intermediate array
Pass 2: map → allocates another intermediate array
Pass 3: slice → allocates final array
Total: 3 passes, 2+ allocations
Single-pass (lazy):
1 loop, processes each element through filter→map→take inline
Stops after 100 elements emitted
Total: 1 partial pass, 0 intermediate allocationsFuture: Compile-Time Fusion
Phase 2 will add compile-time analysis via the typesugar macro system. The lazy macro will inspect the full method chain at compile time and emit a hand-optimized loop — no LazyPipeline class at runtime at all.
// Phase 2 (future): macro rewrites this to a single for-loop
const result = lazy(users)
.filter(u => u.active)
.map(u => u.score)
.sum();
// Compiles to:
// let __sum = 0;
// for (const __el of users) {
// if (__el.active) __sum += __el.score;
// }
// const result = __sum;