Flow
Flow module: backpressure-aware pipeline abstraction.
A Flow pipeline is a chain of stages: producers → transformers → consumers. Demand flows backward (consumers pull from producers), which prevents fast producers from overwhelming slow consumers (backpressure).
DESIGN: In the interpreter, Flow is implemented as a functional pipeline over lazy Seq(a) values (fold-based, church-encoded). The Stage(a) type wraps a Seq(a), and all transformations delegate to the Seq module.
In the compiled runtime, each Stage would be backed by an actor. The consumer sends a Demand(n) message upstream; each stage forwards demand to its source and buffers at most n items before sending them downstream. This is the GenStage/Flow model from Elixir, adapted for March actors.
with_concurrency/2 is a no-op in the interpreter but signals that the stage may execute with n concurrent workers in the compiled runtime.
See stdlib/docs/flow.md for design rationale, usage examples, and a comparison with Elixir's GenStage.
Quick start:
Flow.from_list([1, 2, 3, 4, 5])
|> Flow.map(fn x -> x * 2)
|> Flow.filter(fn x -> x > 4)
|> Flow.collect
-- [6, 8, 10]Stages are lazy: nothing runs until a consumer (collect, reduce, count, find, ...) is applied, and each consumer re-runs the whole pipeline.
Types
A Stage wraps a Seq(a). In the compiled runtime this would be an actor Pid; here it holds the lazy fold function directly.
Functions
True if all items satisfy pred; true for an empty stage.
Examples
march> Flow.all(Flow.range(0, 3), fn x -> x < 2)
false
march> Flow.all(Flow.range(0, 0), fn x -> x < 2)
trueTrue if any item satisfies pred; false for an empty stage.
Examples
march> Flow.any(Flow.range(0, 3), fn x -> x == 2)
trueGroup items into lists of n elements (n must be positive); the last group holds the remainder and may be shorter.
Examples
march> Flow.collect(Flow.batch(Flow.range(0, 5), 2))
[[0, 1], [2, 3], [4]]Run the pipeline and collect all items into a List, in order.
Examples
march> Flow.from_list([1, 2, 3, 4, 5]) |> Flow.map(fn x -> x * 2) |> Flow.filter(fn x -> x > 4) |> Flow.collect
[6, 8, 10]Count the total number of items produced.
Examples
march> Flow.count(Flow.range(0, 7))
7Skip the first n items (nothing is skipped if n <= 0).
Examples
march> Flow.collect(Flow.drop(Flow.range(0, 5), 3))
[3, 4]Run f on each item for its side effects and return :ok. As with Seq.each, f must currently return an Atom such as :ok.
Flow.each(Flow.from_list(["a", "b"]), fn s -> do
println(s)
:ok
end)Keep only items where pred returns true.
Examples
march> Flow.collect(Flow.filter(Flow.range(0, 5), fn x -> x > 2))
[3, 4]Return the first item satisfying pred as Some, or None. The rest of the pipeline still runs; its items are ignored.
Examples
march> Flow.find(Flow.range(0, 10), fn x -> x * x > 20)
Some(5)
march> Flow.find(Flow.range(0, 3), fn x -> x > 5)
NoneApply f to each item and flatten the results. f must return a Stage (e.g. Flow.from_list(...)), not a List.
Examples
march> Flow.collect(Flow.flat_map(Flow.from_list([1, 2]), fn x -> Flow.range(0, x)))
[0, 0, 1]Create a stage from a list of items.
Examples
march> Flow.collect(Flow.from_list([1, 2, 3]))
[1, 2, 3]Create a stage from an existing Seq.
Examples
march> Flow.collect(Flow.from_seq(Seq.from_list([1, 2])))
[1, 2]Consume with f(acc, item) -> acc (alias for reduce).
Examples
march> Flow.into(Flow.from_list(["a", "b"]), "", fn (acc, x) -> acc ++ x)
"ab"Apply f to every item.
Examples
march> Flow.collect(Flow.map(Flow.from_list([1, 2, 3]), fn x -> x * 2))
[2, 4, 6]Create a stage over the integer range [start, stop), counting up by 1. Empty when start >= stop.
Examples
march> Flow.collect(Flow.range(0, 5))
[0, 1, 2, 3, 4]
march> Flow.collect(Flow.range(5, 5))
[]Reduce all items to a single value using f(accumulator, item), starting from start. Returns start for an empty stage.
Examples
march> Flow.reduce(Flow.range(1, 5), 0, fn (acc, x) -> acc + x)
10Run the pipeline for side effects; discard all items and return :ok.
Note: this passes a ()-returning callback to Seq.each, which the typechecker currently rejects (it expects an Atom), so use each with an :ok-returning callback instead.
Keep at most the first n items (none if n <= 0). Upstream stages still run to completion; only the extra items are discarded.
Examples
march> Flow.collect(Flow.take(Flow.range(0, 10), 3))
[0, 1, 2]Create a stage by unfolding a seed value. next : seed -> Option((item, new_seed)) Produces items until next returns None.
next is re-run from seed each time the stage is consumed. If it never returns None the stage is unbounded and no consumer terminates on it (not even after take).
Examples
march> Flow.collect(Flow.unfold(0, fn s -> if s >= 5 do None else Some((s, s + 1)) end))
[0, 1, 2, 3, 4]Parallelism hint: in the compiled runtime, spawn n worker actors for this stage. Currently a no-op everywhere: it returns stage unchanged.
Flow.from_list(urls)
|> Flow.map(fn url -> Http.get(url))
|> Flow.with_concurrency(8)
|> Flow.collect