Stream — FlatMap (sub-stream per item) — stream¶
Status: Awaiting use case — deferred until a concrete driver appears. ← Back to Roadmap
See also: Feature: Reactive Streams ·
streamon pkg.go.dev
This is the surviving item from the retired Phase-4 stream roadmap. The other Phase-4 items have shipped or been resolved:
- GroupBy + Switch routing — implemented in
stream/route.go(GroupBy,Switch,SwitchKey,OfType,SwitchType2/3,SplitEither); see the routing section of the stream guide. - CombineLatest5+ — resolved via nested composition, documented in the stream guide.
FlatMap[In, Out] — sub-stream per input item¶
Why deferred¶
FlatMapSlice[In,Out] covers the common case: one input item expands
synchronously into []Out. The sub-stream variant is different: each input
item spawns a Stream[Out] that may emit items over time, potentially
concurrently.
The main real-world driver has been absorbed elsewhere: "per item, produce
N results through IO" is now ports.IOPort + a 1→N adapter
(sql.QueryEachAdapter, file.ReadAdapter, nethttp.CallAdapter, …) —
sequential per item, with codec validation and observer integration built in.
What remains for FlatMap is the narrower "concurrent sub-streams of pure
computation" case, which no example or user has needed yet.
Proposed API¶
// FlatMap maps each value item to a Stream[Out], then merges all sub-streams
// into a single output stream. maxConcurrency limits how many sub-streams can
// be active simultaneously (0 = unbounded, which is rarely safe).
//
// Errors from src and from active sub-streams all propagate to Stream.Errors.
//
// Use FlatMapSlice for the simpler synchronous one-to-many case.
func FlatMap[In, Out any](
ctx context.Context,
src Stream[In],
fn func(In) Stream[Out],
maxConcurrency int,
) Stream[Out]
Implementation notes¶
- A semaphore channel (
make(chan struct{}, maxConcurrency)) controls concurrency - Each sub-stream goroutine acquires a semaphore slot, forwards values and errors, then releases
- When
maxConcurrency == 0: launch without limit (document as footgun) sync.WaitGroupto close output channels after all sub-streams finish- Merge order decision: unordered (first sub-stream to produce wins) — simpler and faster; ordered merge requires buffering with no known consumer
Trigger for implementation¶
A concrete use case that needs concurrent per-item sub-streams of pure
computation (IO-driven cases should use ports.IOPort + 1→N adapters instead).
Out of scope (will not implement)¶
RetryWithBackoff[T]— design incompatible with the error model:Stream.Errorscarrieserror, not the original item, so retry can't re-invoke the computation. Correct pattern: wrap the forge function in a retry loop beforestream.Apply.- Integration test with real MQTT broker in CI — CI infrastructure concern; not a library feature.