expand
Maps each element to an iterable, then flattens the results one level — the same contract as Iterable.expand.
Lecture
expand is map followed by one level of
flattening: each element is turned into a collection of
results, and those collections are spliced together into a single flat
lazy sequence. It's the tool for "one input, many outputs" — splitting a
sentence into words, expanding a user into their orders, turning a range
into pairs. expand is the Dart-idiomatic name (it matches
Iterable.expand);
fxdart also accepts the FxTS spelling flatMap — they're the
same operator.
FxTS deviation: in FxTS, the callback can return any mix
of plain values and iterables and the operator figures out what
to flatten via DeepFlat type magic. Dart has no equivalent
of that conditional type, so the Dart port requires f to
always return an Iterable<B> — exactly like
Iterable.expand.
Return a single-element list ([x]) to emit exactly one
value per input, or an empty list to emit none.
On the async side, expandAsync's internal state machine
has to track "which sub-iterable am I currently draining" between pulls,
so it consumes its upstream serially — wrapping it in
.concurrent(n) only speeds up pulling already-available
items, not an await that happens inside the callback
itself. If you need concurrent async work per element, do that work in
a .map(...).concurrent(n) stage first, then
.expand((list) => list) to flatten the already-resolved
lists — see Demo 2.
Demo 1 · Basics
The callback must return an Iterable — here, a 2-element
list and a call to String.split:
Demo 2 · Async, the right way to get concurrency
Put the slow await in a .map(...).concurrent(n)
stage; let expand flatten the results synchronously:
Try it yourself
Exercise: use expand to split each sentence into words,
producing one flat list of words.
map — transform without flattening ·
flat — flatten an already-nested iterable ·
mapEffect — map for side effects ·
concurrent — parallel evaluation