How do I use collect as to aggregate stream results in EK9?

← Collections and Data Structures · Ref: Q122

EK9 stream pipelines use | collect as Type to gather results. Built-in collectors handle List, Integer, and String. For custom aggregation, define operator | on a record.

BUILT-IN COLLECTORS

Collect into a list:

  evens <- cat numbers | filter by isEven | collect as List of Integer

Collect by summing integers:

  total <- cat numbers | collect as Integer

Collect by concatenating strings:

  joined <- cat words | collect as String

CUSTOM AGGREGATOR PATTERN

Define a record with operator | to receive stream items one at a time:

  Stats
    count as Integer: Integer()
    total as Integer: Integer()
    operator |
      -> arg as Integer
      if arg?
        if ~total?
          count := 1
          total :=: arg
        else
          count++
          total += arg

The stream feeds each item to operator | in sequence.

TRI-STATE FIRST-ITEM DETECTION

Use ~total? to detect the first item (total is unset). On first item, initialize with :=: (copy operator). On subsequent items, accumulate with +=.

USAGE

  stats <- for i in 1 ... 12 | collect as Stats

The for-range generates integers 1 through 12, piped into the Stats aggregator.

See Q80 for for-range expression as alternative accumulation. See Q82 for for-in expression as alternative fold/reduce. See Q89 for basic collect patterns. See Q96 for operator overloading. See Q120 for sort before collect. See Q124 for group by aggregation. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide. See Q286 for batch accumulation with stream collect.

Example

defines module qa.collections.collectas

  defines record

    Stats
      count as Integer: Integer()
      total as Integer: Integer()
      average as Float: Float()

      operator $ as pure
        <- rtn as String: String()
        if total?
          rtn :=? `[${count}, ${average}, ${total}]`

      operator |
        -> arg as Integer

        if arg?
          if ~total?
            count := 1
            total :=: arg
          else
            count++
            total += arg
          average := (#^total) / count

      default operator ?

  defines function

    isEven() as pure
      -> num as Integer
      <- rtn as Boolean: num mod 2 == 0

    doubleIt() as pure
      -> num as Integer
      <- rtn as Integer: num * 2

  defines program

    CollectAsDemo()
      stdout <- Stdout()

      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

      // === BUILT-IN: COLLECT AS LIST ===

      evens <- cat numbers | filter by isEven | collect as List of Integer
      stdout.println(`Evens: ${evens}`)

      // === BUILT-IN: COLLECT AS INTEGER (SUM) ===

      total <- cat numbers | collect as Integer
      stdout.println(`Sum: ${total}`)

      // === CUSTOM AGGREGATOR ===

      stats <- for i in 1 ... 12 | collect as Stats
      stdout.println(`Stats: ${stats}`)

      // === CUSTOM AGGREGATOR WITH FILTER ===

      evenStats <- cat numbers | filter by isEven | collect as Stats
      stdout.println(`Even stats: ${evenStats}`)

Common mistakes

E50060 — EK9 does not have .stream() or .collect() methods. Use the cat source | filter by pred | collect as Type pipeline syntax. See ek9 -h E50060 for details.

Incorrect:

evens <- numbers.stream().filter(isEven).collect()

Correct:

evens <- cat numbers | filter by isEven | collect as List of Integer

E07235 — A record with fields must define operator ? for tri-state semantics. A Stats record used with '| collect as' needs isSet so the pipeline can check if the result is valid. See ek9 -h E07235 for details.

Incorrect:

//no isSet operator

Correct:

default operator ?

E50060 — EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details.

Incorrect:

stdout.println(evens.toString())

Correct:

stdout.println(`Evens: ${evens}`)

E50060 — EK9 does not have toString(). Use string interpolation or the $ operator for string conversion. See ek9 -h E50060 for details.

Incorrect:

stdout.println(total.toString())

Correct:

stdout.println(`Sum: ${total}`)

E07520 — A function used with '| filter by' must return Boolean. The function doubleIt() returns Integer, not Boolean, so it cannot be used as a predicate. Use a function that returns Boolean for filtering. See ek9 -h E07520 for details.

Incorrect:

cat numbers | filter by doubleIt | collect as List of Integer

Correct:

cat numbers | filter by isEven | collect as List of Integer
Other ways to ask this
  • How do I create a custom stream collector in EK9?
  • How does the operator | work for stream aggregation in EK9?
  • How do I reduce a stream to a single value in EK9?
  • What is the EK9 equivalent of fold or reduce?
  • How do I accumulate values from a stream in EK9?

Coming from another language?

Java: Collectors.reducing(), Collectors.summarizingInt(), custom Collector interface. Python: functools.reduce() or manual accumulation. JavaScript: array.reduce((acc, item) => ..., initial). Rust: iter.fold(initial, |acc, item| ...). Go: manual loop accumulation. EK9: define operator | on a record, use | collect as MyRecord in pipeline, tri-state ~var? for first-item detection.

Keywords: operator, reduce, accumulator, collect, sum, reducer, pipe, data-structure, custom, result, collection, aggregate, stream, fold, accumulate, statistics