8

existingIterator任意の type の要素を反復処理するものがあると仮定しましょう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反復子の引数がないため、ループごとに 1 回評価する必要がある引数と、ループの反復ごとに 1 回評価する必要がある引数を区別する方法がありません。

クロージャ イテレータを引数として別のクロージャ イテレータに渡す場合infinite、ループを通過するたびに、新しい環境を持つイテレータの新しいインスタンスが作成されることを意味します。これによりinfinite、何度もゼロから開始されます。

インライン イテレータは、通常、ループごとに 1 回だけ引数を評価します (ほとんどの場合、これは予期される動作です)。クロージャー イテレーターは、その本体をステート マシンに変換する必要があります。これにより、呼び出し方法が変更されます。また、別の方法で使用することもできます。特に、クロージャー イテレーターは、インライン イテレーターとは異なり、複数の呼び出しサイトを持つことができます。例えばlet iter = ...; iter(someArgument); iter(someOtherArgument)。その結果、ここでバグを見ているのか、それとも意図した動作を見ているのかわかりません。

infinitetake直接渡すのではなく、letfirstを使用することで、これを修正できます。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 に答える