How do I create a custom iterator so my type works with cat and for-in?
← Streams and Pipelines · Ref: Q236
To make a custom type work with cat (stream source) and for-in loops, your type needs to provide an iterator() method. EK9 uses the iterator pattern: your type returns an Iterator of T, which the stream or loop consumes.
THE ITERATOR CONTRACT
An iterator must support two operations:
hasNext() Returns true if more elements are available. next() Returns the next element and advances.
The for-in loop calls hasNext() before each iteration and next() to get each element. The cat operation does the same to feed elements into a pipeline.
PATTERN 1: DELEGATE TO A COLLECTION
The simplest approach is to store items in a List and delegate iteration:
Library
books as List of Book
iterator() as pure
<- rtn as Iterator of Book: books.iterator()
This is the most common pattern. Any class that wraps a collection can expose it for iteration.
PATTERN 2: DYNAMIC CLASS ITERATOR
For lazy generation (values computed on demand), create a dynamic class that implements Iterator of T:
NumberRange
iterator()
<- rtn as Iterator of Integer: createIterator(start, limit)
createIterator()
-> start as Integer, limit as Integer
<- rtn as Iterator of Integer?
current <- Integer(start)
rtn: (current, limit) is Iterator of Integer as class
override hasNext() as pure
<- rtn as Boolean: current? and limit? and current <= limit
override next()
<- rtn as Integer: Integer(current)
current++
default operator ?
The dynamic class captures 'current' and 'limit' by value. Each call to next() advances the internal position. The hasNext() method checks if more elements remain.
USING WITH FOR-IN
Once your type has iterator(), for-in works automatically:
range <- NumberRange(1, 5) for num in range stdout.println($num)
USING WITH CAT (STREAM SOURCE)
The same type works as a stream source:
range <- NumberRange(1, 10) evens <- cat range | filter by isEven | collect as List of Integer
This is the bridge between OOP types and functional stream processing. Any type with iterator() becomes a first-class stream source.
FRESH ITERATORS
The iterator() method should create a fresh iterator each time. This allows multiple independent iterations over the same source.
BUILT-IN ITERABLE TYPES
These types already provide iterators: List, Dict (iterates DictEntry values), Optional (yields 0 or 1 elements), Result (yields ok value if present), and all enumeration types.
See Q65 for for-in loops. See Q85 for Optional as iterator. See Q87 for Result as iterator. See Q89 for stream pipeline basics. See Q99 for enumeration iteration. See Q115 for dynamic classes. See Q224 for enum streams. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide.
Example
defines module qa.streams.customiterators defines class NumberRange start as Integer? limit as Integer? default private NumberRange() as pure NumberRange() as pure -> start as Integer limit as Integer this.start :=? start this.limit :=? limit iterator() <- rtn as Iterator of Integer: createIterator(start, limit) default operator ? defines function createIterator() -> start as Integer limit as Integer <- rtn as Iterator of Integer? current <- Integer(start) rtn: (current, limit) is Iterator of Integer as class override hasNext() as pure <- rtn as Boolean: current? and limit? and current <= limit override next() <- rtn as Integer: Integer(current) current++ default operator ? isEven() as pure -> num as Integer <- rtn as Boolean: num mod 2 == 0 defines program CustomIteratorDemo() stdout <- Stdout() // === FOR-IN WITH CUSTOM ITERATOR === range <- NumberRange(1, 5) stdout.println("For-in iteration:") for num in range stdout.println(` ${num}`) // === CAT WITH CUSTOM ITERATOR (stream source) === range2 <- NumberRange(1, 10) evens <- cat range2 | filter by isEven | collect as List of Integer stdout.println(`Even numbers 1-10: ${evens}`) // === MULTIPLE ITERATIONS (fresh iterator each time) === range3 <- NumberRange(1, 3) first <- cat range3 | collect as List of Integer second <- cat range3 | collect as List of Integer stdout.println(`First pass: ${first}`) stdout.println(`Second pass: ${second}`)
Common mistakes
E50060 — Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.
Incorrect:
stdout.display(`Even numbers 1-10: ${evens}`)
Correct:
stdout.println(`Even numbers 1-10: ${evens}`)
Other ways to ask this
- How do I make my own type iterable in EK9?
- How do I implement the iterator pattern for a custom class in EK9?
- How do I stream my own type with cat in EK9?
Coming from another language?
Java: implement Iterable<T> with iterator() method, Iterator<T> with hasNext() and next(), for-each loop and Stream.of() consume iterables. Python: implement __iter__() returning self with __next__(), raise StopIteration when done, for-in and list comprehensions consume iterables. Rust: implement Iterator trait with next() returning Option<T>, for-in and .iter() consume iterators, IntoIterator for owned iteration. Go: no iterator interface, manual for-range with index, channels for generator pattern. Kotlin: implement Iterable<T> or Iterator<T>, sequence builders for lazy generation. JavaScript: implement Symbol.iterator returning object with next() returning value and done. EK9: provide iterator() method returning Iterator of T, use dynamic class is Iterator of T as class with override hasNext() and override next(), works with both for-in loops and cat stream pipelines.
Keywords: dynamic, class, stream, capture, next, iterator, pipe, for-in, hasNext, source, pipeline, pattern, define, iterable, loop, anonymous, implement, custom, cat, type, closure