tee
Runs several folds over one pass of a source — one iteration, nothing buffered.
Lecture
Two questions about the same data usually cost two passes:
readings.fold(...) for the total, then
readings.reduce(...) for the peak. That is fine for a
List, and wrong for anything else — a sync*
generator, a network page, a source that counts how often it ran will
all be walked twice. tee asks both questions at once:
each element advances the total and the peak before the next
one is pulled, so the source is iterated exactly once.
A reader is given as a fold — a record of
seed (where it starts) and step (how one
element advances it). That shape is what makes the single pass free.
Because both readers move together, element by element, there is never
a value that one has seen and the other has not, so there is nothing
to remember: tee over a million elements holds two
accumulators, not a million values. The two accumulators are entirely
independent and need not share a type. tee3 takes three.
The constraint is the price of that. tee feeds folds, not
pipelines — the readers cannot advance at their own pace, take
different amounts, or stop early independently. When you genuinely need
two independent readers, reach for
fork instead, and accept the shared
buffer it keeps so a lagging cursor can catch up. Rule of thumb: if both
readers consume the whole source and reduce it to a value,
tee; if either one is a pipeline in its own right,
fork.
Where the name comes from
tee is not an abbreviation — it is the letter T,
borrowed from the T-splitter used in plumbing. A T-fitting branches one
pipe into two, so what flows in one way leaves two ways at once. Unix
took the image for its tee command, which reads standard
input and sends it to standard output and a file at the same
time:
input
│
▼
┌───┴───┐
│ tee │
└───┬───┘
┌────┴────┐
▼ ▼
stdout file
Python's itertools.tee() borrows the same picture, splitting
one iterable into several independent iterators. Worth knowing, because
that is the part FxDart spells fork,
not tee: fork gives you independent cursors,
the way Python's does. FxDart's tee branches the
consumption instead — one pass, several folds reading it in
step. Same T-shaped picture, split one level further downstream.
Demo 1 · Total and peak from one read
sensor() increments reads for every value it
produces. Two separate passes would leave reads at 12;
tee leaves it at 6:
Demo 2 · Independent accumulators, tee3, and on a chain
The two folds carry unrelated types — an int character
count beside a String running winner. tee3
adds a third fold, and on an fx chain the folds see what
the chain produces, not the original source:
Try it yourself
Exercise: sensor() is currently walked twice, so
reads prints 6. Replace the two passes with a single
tee — summing in one fold and counting in the other — so
that reads prints 3.