Collect a pipeline into what you need
Reach for the named terminal operation when there is one - toList, sum, joined, groupBy - and fall back to collect with an Accumulator for anything else, including your own accumulator.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Nothing in a pipeline runs until its terminal operation pulls the values through. Most of the time the result you
need already has a name on Iterate itself; collect with an Accumulator is what is left for everything that does
not.
Steps#
Reach for the named terminal operation first.
toList(),toSet(),to<Target>()for any otherFrom<Iterate<Item>>,sum(),count(),joined(separator:),fold(initial, combine),first(),find { ... },any { ... },all { ... }andgroupBy { ... }cover most pipelines without ever naming anAccumulator.const total = orders.map { order => order.amount }.sum() const byCity = customers.groupBy { customer => customer.city }Reach for
collectwith a standard-libraryAccumulatorfor a shape none of those name.counting(),summing { ... },averaging { ... },minBy { ... }/maxBy { ... },partitioningBy { ... }, andgroupingBy { ... }.then(downstream)for one collector per group.const stats = orders.collect(groupingBy { order: Order => order.city }.then(summing { order: Order => order.amount }))Run the same accumulator more than once when several results come from one pipeline of values. An
Accumulatoris a value; each call tocollectfills a copy of its own.const amounts = summing { order: Order => order.amount } const thisMonth = orders.filter { order => order.isThisMonth() }.collect(amounts) const lastMonth = orders.filter { order => order.isLastMonth() }.collect(amounts)Write
collector(initial, finish:, step:)when the state a run needs is one value. It answers anAccumulatorwhose accumulator foldsstepover every value and appliesfinishonce, which covers most custom aggregates without a type of their own.Write a
typewithvarfields that implementsAccumulatorwhen the state is more than one value, the way the standard library's owngroupingBy(...).then(...)keeps one accumulator per group in aMap.
Pitfalls#
isDone()is what lets a terminal operation stop early.first(),find { ... }andcollectbehindtake(n)never read a source to the end; a customAccumulatorthat never overridesisDone()is asked to read everything, which is correct for a sum or a count and wrong for anything that could stop sooner.- An
Accumulatorvalue is not itself a result.orders.collect(summing { order: Order => order.amount })runs it; the value on its own is a description that can be handed tocollectagain for a different source. foldand a hand-writtenAccumulatordo the same pull. Reach forfoldwhen the state is a simple running value and there is only one call site; reach for a namedAccumulatorthe moment two pipelines need the same aggregate, so the logic is written once.- A collection is not an
Accumulator.pipeline.collect(existingList)is not a thing: name the accumulator that gathers into one -listing()orListAccumulator<Item>()for a list,into<Set<Int>>()for anything withFrom<Iterate<Item>>- and append the result to what you already have.
Full example#
type Order {
city: String
amount: Int
}
const orders = [
Order("Berlin", 120),
Order("Berlin", 80),
Order("Munich", 200),
]
const total = orders.map { order => order.amount }.sum()
print total
const totalsByCity = orders.collect(groupingBy { order: Order => order.city }.then(summing { order: Order => order.amount }))
print totalsByCity
const highValue = orders.collect(partitioningBy { order => order.amount >= 100 })
print highValueRelated#
- Pipelines - source, lazy stages and terminal operation, in full.
- Collectors -
Accumulatorand every standard collector in one place. - Sort by more than one key - a
sortedstage in front of the same kind of pipeline.