How do I use functions in stream pipelines?
← Getting Started · Ref: Q59
Dynamic functions with variable capture are essential for EK9 stream pipelines. They provide context to pipeline stages like 'map with' and 'filter by' without global state or side effects.
BASIC PIPELINE WITH NAMED FUNCTIONS
cat items | map with transformer | filter by checker | collect as List of String
Each stage uses a function reference. 'map with' applies a transformation function. 'filter by' uses a predicate function.
CAPTURING CONTEXT FOR PIPELINE STAGES
The real power comes from dynamic functions that capture variables for use in pipeline stages:
names <- Dict() of (Integer, String) names += DictEntry(1, "Alice") names += DictEntry(2, "Bob")
idToName <- (names) is idMapper as function result: names.getOrDefault(id, "Unknown")
resolved <- cat [1, 2, 3] | map with idToName | collect as List of String
The dynamic function 'idToName' captures the 'names' Dict and uses it inside the pipeline. Each pipeline element gets the captured context without global state.
STATEFUL PIPELINE FUNCTIONS
Dynamic functions can hold state across pipeline iterations — useful for accumulation, counting, or multi-element processing:
count <- 0 counter <- (count) is someAccumulator as function count++ result:=? count
This is explicitly stated as a design feature: dynamic functions provide real power when building stream pipelines where you want to retain state as part of the pipeline process rather than depending on a single reduce at the end.
INLINE FUNCTIONS IN PIPELINES
Use inline syntax directly in the pipeline:
results <- cat keys | map with (names) is idMapper (result: names.getOrDefault(id, "Unknown")) | collect as List of String
This is compact but still nominally typed — the compiler verifies the function matches the pipeline stage requirements.
FLATTEN FOR OPTIONAL RESULTS
When a pipeline stage returns Optional values, use 'flatten' to extract set values and discard unset ones:
results <- cat keys | map with lookupFunction | flatten | collect as List of String
This eliminates null checking in pipeline code.
In Java, streams use lambdas but capture is hidden and by reference. In Python, generators and comprehensions have no type safety. In Rust, iterators with closures require complex lifetime annotations. EK9 pipelines with dynamic function captures are type-safe, explicit, and maintain quality enforcement.
See Q51 for abstract functions used as pipeline stage types. See Q52 for dynamic functions. See Q53 for variable capture. See Q54 for Consumer patterns in pipeline callbacks. See Q45 for List operations. See Q46 for Dict operations. See Q89 for stream pipeline basics. See Q122 for collect as and custom aggregators. See Q235 for complete stream operations reference.
Example
defines module qa.functionpipeline defines function nameMapper() as abstract -> identifier as Integer <- result as String? defines program PipelineDemo() stdout <- Stdout() // Set up context data names <- Dict() of (Integer, String) names += DictEntry(1, "Alice") names += DictEntry(2, "Bob") names += DictEntry(3, "Charlie") // Dynamic function captures Dict for pipeline use lookup <- (names) is nameMapper as function result: names.getOrDefault(identifier, "Unknown") // Pipeline with captured context ids <- [1, 2, 4, 3] results <- cat ids | map with lookup | collect as List of String for name in results stdout.println(`resolved: ${name}`) // Direct function call — same function works outside pipelines stdout.println(`direct: ${lookup(2)}`)
Common mistakes
E07880 — Using 'call' instead of 'map with' for a function that takes a parameter and returns a value triggers E07830 — stream pipeline type mismatch. Use 'map with' for transformation functions. See ek9 -h E07830 for details.
Incorrect:
| call lookup
Correct:
| map with lookup
E50060 — EK9 Dict does not have a get() method. Use getOrDefault(key, default) for safe access. Triggers E50060 — method not resolved. See ek9 -h Dict for the full API.
Incorrect:
names.get(identifier, "Unknown")
Correct:
names.getOrDefault(identifier, "Unknown")
Other ways to ask this
- How do dynamic functions work with EK9 stream pipelines?
- How do I capture variables for use in a pipeline stage?
- How do functions integrate with cat, map, and filter in EK9?
Coming from another language?
Java: Stream API with lambdas, hidden capture by reference, map/filter/reduce pattern, no explicit capture list, lambda quality not enforced, flatMap for Optional unwrapping. Python: generators, list comprehensions, map/filter built-ins, no type safety, closures capture by reference. JavaScript: Array.map/filter/reduce with arrow functions, no type safety, promise chains, hidden capture. Rust: Iterator trait with map/filter/collect, closures with complex lifetime annotations, move semantics for capture, turbofish syntax for type annotation. Go: no built-in stream pipelines, manual loops, channels for pipeline patterns, verbose. Kotlin: sequences with lambda chains, structural typing, inline functions for performance, no explicit capture. Swift: lazy sequences with closures, structural typing, no explicit capture list in pipeline context. EK9: stream pipelines with cat/map/filter/collect, dynamic functions with EXPLICIT by-value capture provide pipeline context, stateful functions for accumulation, inline syntax for compact stages, flatten for Optional unwrapping, type-safe and quality-enforced.
Keywords: closure, beginner, start, stream, dynamic, context, anonymous, filter, stateful, intro, pipeline, first, function, capture, map, flatten, migrate, collect, cat, stage