【问题标题】:Nim: How to wrap/derive an iterator from another iterator?Nim:如何从另一个迭代器包装/派生一个迭代器?
【发布时间】:2015-06-17 18:16:07
【问题描述】:

假设我们有一些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.})`

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

【问题讨论】:

  • 有一个open issue 可能与此问题有关。
  • Jon Skeet 有一篇关于在 C# 中为 LINQ 执行此操作的优秀博客文章系列 - 这与您正在做的事情非常相似(尽管使用另一种语言),但总体思路相同。
  • 为什么他们不能称这种语言为别的该死的东西,看到我的名字出现在他们身上真是令人困惑..:/
  • @Nim:至少不像约翰。或者朱莉娅:)。

标签: templates iterator nim-lang


【解决方案1】:

问题出在

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

【讨论】:

  • 非常感谢,很好的解释!我也喜欢你在 Github 上的提议。在所有这些示例中,“一次”参数可能真的很有帮助。
  • 稍后的几个 Nim 版本...如果infinite 也接受参数,在countUpFrom(1) 的意义上会怎样(这是不可调用的,因此 let 绑定不起作用)。我仍然通过迭代器实验不断收到attempting to call undeclared routineinf.take(10).take(9) 的同样问题。
  • 回复对于评论来说太长了,所以我更新了我的答案。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-17
    • 2013-06-17
    • 2016-07-24
    • 2012-10-23
    • 2014-08-07
    • 2010-10-09
    • 2018-09-23
    • 2015-09-20
    相关资源
    最近更新 更多