Nim:如何从另一个迭代器中包装/派生迭代器?

blu*_*e10 7 templates iterator nim-lang

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

  • 限制原始迭代器的长度,例如existingIterator.take(n).
  • 映射元素,例如, existingIterator.map(modifier)
  • 过滤某些元素,例如existingIterator.filter(predicate).

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

for x in existingIterator.filter(something)
                         .map(modifier)
                         .take(10):
  ...
Run Code Online (Sandbox Code Playgroud)

我的一般问题是:如何编写一个通用的迭代器或模板,它接受一个现有的迭代器并返回一个修改过的迭代器?

后续问题将是为什么这些基本功能不在标准库中 - 也许我错过了什么?


这是我尝试过的:

尝试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
Run Code Online (Sandbox Code Playgroud)

这编译,但不幸的是,它并没有真正起作用:(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
Run Code Online (Sandbox Code Playgroud)

这似乎工作(即模板编译).但是,如果我现在打电话给for x in infinite.take(10)我:

`Error: type mismatch: got (iterator (): int{.closure, gcsafe, locks: 0.})`
Run Code Online (Sandbox Code Playgroud)

我试图追加一个()实际上"调用"迭代器,但它仍然不起作用.所以它归结为一个问题:我应该如何从模板构造/返回迭代器?

Rei*_*nds 8

问题在于

for x in infinite.take(10):
  echo x
Run Code Online (Sandbox Code Playgroud)

或者,更具体地说,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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)