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

时间:2015-04-12 10:21:25

标签: templates iterator nim

假设我们有一些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)元素没有正确迭代(它们都只是零,可能是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.})`

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

2 个答案:

答案 0 :(得分: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,可以通过将迭代器包装在模板或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

答案 1 :(得分:2)

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

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