Let's assume we have some existingIterator
which iterates over elements of an arbitrary type T
. What I now want to achieve is to derive a new iterator from existingIterator
with a modified behavior. Think of examples like:
- Limiting the length of the original iterator, e.g.,
existingIterator.take(n)
. - Mapping over the elements, e.g.,
existingIterator.map(modifier)
- Filtering certain elements, e.g.,
existingIterator.filter(predicate)
.
In all these cases I simply want to produce yet another iterator so that I can do something like that:
for x in existingIterator.filter(something)
.map(modifier)
.take(10):
...
My general problem is: How can I write a generic iterator or template which takes an existing iterator and returns a modified iterator?
A follow-up question would be why such essential functionality is not in the standard library -- maybe I'm missing something?
Here is what I have tried:
Attempt 1
Let's take the take(n)
functionality as an example. My first approach was to use a regular generic iterator
:
iterator infinite(): int {.closure.} =
var i = 0
while true:
yield i
inc i
iterator take[T](it: iterator (): T, numToTake: int): T {.closure.} =
var i = 0
for x in it():
if i < numToTake:
yield x
inc i
for x in infinite.take(10):
echo x
This compiles, but unfortunately, it does not really work: (1) the elements are not properly iterated (they are all just zero, maybe a bug?), (2) it looks like my program is stuck in an endless loop, and (3) it only works for closure iterators, which means that I cannot wrap arbitrary iterators.
Attempt 2
The limitation to closure iterators suggests that this problem actually requires a template solution.
template take[T](it: iterator(): T, numToTake: int): expr {.immediate.} =
var i = 0
iterator tmp(): type(it()) =
for item in it:
if i < numToTake:
yield item
inc i
tmp
This almost seem to work (i.e., the template compiles). However, if I now call for x in infinite.take(10)
I get:
`Error: type mismatch: got (iterator (): int{.closure, gcsafe, locks: 0.})`
I tried to append a ()
to actually "call" the iterator, but it still doesn't work. So it comes down to the question: How should I construct/return an iterator from a template?