How do I use tee and uniq in stream pipelines in EK9?
← Collections and Data Structures · Ref: Q133
EK9 provides tee for capturing intermediate pipeline state and uniq for removing duplicate elements. Both are pipeline operations that integrate with cat | collect syntax.
TEE FOR SIDE EFFECTS
Tee copies each element to a variable while passing it through:
captured <- List() of String cat items | tee in captured | collect as List of String
After the pipeline, captured contains all items that flowed through that point. Useful for debugging or capturing intermediate state.
TEE FOR DEBUGGING
Insert tee at any point in a pipeline to observe values:
cat items | filter by isValid | tee in afterFilter | map with transform | collect as List of String
afterFilter shows what passed the filter before transformation.
UNIQ BY HASHCODE
Remove consecutive duplicates using the #? (hashcode) operator:
cat items | sort | uniq | collect as List of String
Sort first to ensure duplicates are adjacent. Uses the element's #? operator for comparison.
UNIQ BY KEY EXTRACTOR
Remove duplicates based on a specific field:
cat items | uniq by extractKey | collect as List of Item
The key extractor function determines what counts as a duplicate.
COMBINING TEE AND UNIQ
Capture state before deduplication:
cat items | sort | tee in beforeUniq | uniq | collect as List of String
beforeUniq has all sorted items; the result has duplicates removed.
See Q89 for basic stream pipelines. See Q96 for hashcode operator (#?). See Q120 for sort (needed before uniq). See Q124 for group by. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide.
Example
defines module qa.collections.teeuniq defines function firstChar() as pure -> word as String <- rtn as String: $#<word longEnough() as pure -> word as String <- rtn <- Boolean() expectedCount <- 4 rtn: length word >= expectedCount defines program TeeUniqDemo() stdout <- Stdout() words <- ["banana", "apple", "cherry", "apple", "banana", "date", "cherry", "elderberry"] // === TEE: CAPTURE INTERMEDIATE STATE === afterFilter <- List() of String result <- cat words | filter by longEnough | tee in afterFilter | sort | collect as List of String stdout.println(`After filter (via tee): ${afterFilter}`) stdout.println(`Final sorted: ${result}`) // === UNIQ: REMOVE DUPLICATES === // Sort first to make duplicates adjacent unique <- cat words | sort | uniq | collect as List of String stdout.println(`Unique: ${unique}`) // === UNIQ BY KEY EXTRACTOR === // One word per first character uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of String stdout.println(`Unique by first char: ${uniqueByFirst}`) // === TEE + UNIQ TOGETHER === beforeUniq <- List() of String deduped <- cat words | sort | tee in beforeUniq | uniq | collect as List of String stdout.println(`Before uniq: ${beforeUniq}`) stdout.println(`After uniq: ${deduped}`)
Common mistakes
E50060 — EK9 has no distinct() or stream() methods. Use | sort | uniq in a pipeline to remove consecutive duplicates by hashcode. See ek9 -h E50060 for details.
Incorrect:
unique <- words.stream().distinct().collect()
Correct:
unique <- cat words | sort | uniq | collect as List of String
E07830 — The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer. See ek9 -h E07830 for details.
Incorrect:
unique <- cat words | sort | uniq | collect as List of Integer
Correct:
unique <- cat words | sort | uniq | collect as List of String
E07830 — The collect type must match the pipeline's element type. After uniq by firstChar, the stream still contains String elements, not Integer. See ek9 -h E07830 for details.
Incorrect:
uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of Integer
Correct:
uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of String
E50060 — EK9 has no distinct() or stream() methods. Use | sort | uniq in a pipeline to remove consecutive duplicates by hashcode. See ek9 -h E50060 for details.
Incorrect:
unique <- words.stream().distinct().collect()
Correct:
unique <- cat words | sort | uniq | collect as List of String
E07830 — The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer. See ek9 -h E07830 for details.
Incorrect:
| tee in afterFilter | sort | collect as List of Integer
Correct:
| tee in afterFilter | sort | collect as List of String
E07830 — The tee and collect must match the pipeline type. After uniq the stream still contains Strings, so collecting into List of Integer fails. See ek9 -h E07830 for details.
Incorrect:
| tee in beforeUniq | uniq | collect as List of Integer
Correct:
| tee in beforeUniq | uniq | collect as List of String
E50060 — EK9 List has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details.
Incorrect:
stdout.println(unique.toString())
Correct:
stdout.println(`Unique: ${unique}`)
Other ways to ask this
- How do I capture intermediate stream results in EK9?
- How do I remove duplicates from a stream in EK9?
- How do I debug a stream pipeline in EK9?
- How do I take a side copy of data as it flows through a stream pipeline in EK9?
- How do I observe or inspect elements mid-pipeline in EK9?
Coming from another language?
Java: stream.peek() for side effects (like tee), stream.distinct() for unique elements. Python: no built-in tee (itertools.tee is different), set() for unique. JavaScript: no built-in pipeline tee, [...new Set(arr)] for unique. Rust: iter.inspect() for tee, iter.dedup() for consecutive dedup (like EK9 uniq). Go: manual implementation for both. EK9: | tee in variable for capture, | uniq for dedup by hashcode, | uniq by func for dedup by key.
Keywords: collection, tee, duplicate, tap, capture, copy, inspect, debug, stream, intercept, observe, effect, pipeline, unique, data-structure, uniq, intermediate, side