Nim:如何从另一个迭代器 wrap/derive 一个迭代器?

Nim: How to wrap/derive an iterator from another iterator?

假设我们有一些 existingIterator 迭代任意类型的元素 T。我现在想要实现的是从 existingIterator 派生一个具有修改行为的新迭代器。想想这样的例子:

在所有这些情况下,我只是想生成另一个迭代器,以便我可以做类似的事情:

for x in existingIterator.filter(something)
                         .map(modifier)
                         .take(10):
  ...

我的一般问题是:如何编写通用迭代器或采用现有迭代器和 returns 修改后的迭代器的模板?

后续问题是为什么标准库中没有这样的基本功能——也许我遗漏了什么?


这是我尝试过的:

尝试 1

让我们以take(n)功能为例。我的第一种方法是使用常规泛型 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

这可以编译,但不幸的是,它并没有真正起作用:(1)元素没有正确迭代(它们都只是零,可能是 bug?),(2)它看起来像我的程序陷入无限循环,并且 (3) 它只适用于闭包迭代器,这意味着我不能包装任意迭代器。

尝试 2

闭包迭代器的限制表明这个问题实际上需要一个模板解决方案。

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

这似乎行得通(即模板编译)。但是,如果我现在调用 for x in infinite.take(10),我会得到:

`Error: type mismatch: got (iterator (): int{.closure, gcsafe, locks: 0.})`

我试图将 () 附加到实际上 "call" 迭代器,但它仍然不起作用。所以归结为问题:我应该如何 construct/return 来自模板的迭代器?

问题出在

for x in infinite.take(10):
  echo x

或者,更具体地说,调用 infinite.take(10),我们也可以将其写为 take(infinite, 10)。与 Sather 不同,Nim 的迭代器没有 once 个参数,因此无法区分每个循环应该计算一次的参数和每个循环迭代应该计算一次的参数.

在将闭包迭代器作为参数传递给另一个闭包迭代器的情况下,这意味着每次执行循环时都会创建具有新环境的 infinite 迭代器的新实例。这将使 infinite 一次又一次地从零开始。

内联迭代器通常每个循环只计算一次参数(这是大多数情况下的预期行为)。闭包迭代器必须将它们的主体转换为状态机,这会改变它们的调用方式。它们也可以有不同的使用方式:特别是闭包迭代器可以有多个调用点,这与内联迭代器不同;例如let iter = ...; iter(someArgument); iter(someOtherArgument)。因此,我不确定我们在这里查看的是错误还是预期的行为。

您可以通过不将 infinite 直接传递给 take 来解决此问题,而是首先使用 let。您的 take 代码中还有一个错误,即循环不会终止,您也需要修复该错误。生成的代码类似于:

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:
      break
    yield x
    inc i

let inf = infinite
for x in inf.take(10):
  echo x

如果您想参数化 infinite,这可以通过将迭代器包装在模板或过程中来完成,例如:

template infiniteFrom(x: int): (iterator (): int) =
  (iterator (): int =
    var i = x
    while true:
      yield i
      inc i)

...

let inf = infiniteFrom(1)
for x in inf.take(10):
  echo x

我也曾尝试向 Nim 添加函数式方法,最后我将所有内容都包装在函数中。请看一下http://forum.nim-lang.org/t/1230 这样,您可以在使用 for.

循环之前将迭代器分配给一个变量