8

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

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

在所有这些情况下,我只想生成另一个迭代器,以便可以执行以下操作:

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

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

一个后续问题是为什么标准库中没有这些基本功能——也许我遗漏了一些东西?


这是我尝试过的:

尝试 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)元素没有正确迭代(它们都只是零,可能是一个错误?),(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.})`

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

4

2 回答 2

8

问题在于

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
于 2015-04-15T11:09:40.953 回答
1

我还尝试向 Nim 添加函数方法,最后我将所有内容都包装在函数中。请查看http://forum.nim-lang.org/t/1230 这样您可以在使用 for 循环之前将迭代器分配给变量。

于 2015-05-18T07:21:01.443 回答