Общие изменяемые и неизменяемые параметры итератора

Вот итератор, который я не хочу копировать:

iterator testI[T](arr: seq[T]): T =
  # I don't want to copy-paste this body
  # In a real world example it might be much bigger
  for i in 0 ..< arr.len:
    yield arr[i]

Это работает на:

for i in testI(@[1,2,3]):
  echo i

Но не на:

var lst = @[1,2,3]
for i in testI(lst):
  i += 1
  echo i

Я мог бы заменить определение итератора на:

iterator testI[T](arr: var seq[T]): var T =
  # I don't want to copy-paste this body
  # In a real world example it might be much bigger
  for i in 0 ..< arr.len:
    yield arr[i]

Обратите внимание, я просто добавил var к параметру и вернулся. Но это больше не работает для случая без var выше.

Насколько я могу судить, эта проблема эквивалентна выполнению следующей работы:

proc foo[A, B](x: A, fn: B) =
  fn(x)

var x = 1
foo(x, proc(x: var int) = x += 1)
foo(1, proc(x: int) = echo x)
assert x != 1

Почему-то это даже не работает:

iterator testI[A, B](arr: A): B =
  # I don't want to copy-paste this body
  # In a real world example it might be much bigger
  for i in 0 ..< arr.len:
    yield arr[i]
var lst = @[1,2,3]
for i in testI[var seq[int], var int](lst):
  i += 1

В языке программирования D есть такие параметры, как «автоссылка», которые выбирают либо ссылочные параметры, либо параметры значения в зависимости от того, являются ли они lvalue или нет.

Почему это такая трудная проблема? Я не хочу копировать и вставлять функцию каждый раз, когда я создаю итератор.


person kfa6706    schedule 25.08.2020    source источник


Ответы (1)


Обычно в Nim вы найдете два итератора: items и mitems, один для получения элементов без их изменения, другой для разрешения модификации. Я бы изо всех сил старался использовать эти процедуры, прежде чем пытаться реализовать что-то сумасшедшее.

Но бывают случаи, когда вам нужно это реализовать, поэтому всякий раз, когда вы сталкиваетесь с проблемой повторения кода в Nim, которую можно решить с помощью Ctrl-C Ctrl-V, вы должны мыслить шаблонами. Например.

template myCycle =
  ## Long and tedious code here.
  for i in 0 ..< arr.len:
    yield arr[i]

iterator testI[T](arr: seq[T]): T =
  echo "Non-var type"
  myCycle

iterator testI[T](arr: var seq[T]): var T =
  echo "Var type"
  myCycle

var lst1 = @[1,2,3]
for i in testI(lst1):
  echo i

let lst2 = @[1,2,3]
for i in testI(lst2):
  echo i

Когда вы скомпилируете, Nim скопирует для вас длинный код здесь, в myCycle, и вставит его в оба ваших итератора. Обратите внимание, что вам не нужно передавать arr в шаблон, так как это не вызов, а копипаста.


Нет ничего лучше вашего последнего блока кода, чтобы проиллюстрировать мою первую мысль. Это эквивалентный код:

iterator testI[T](a: var openArray[T]): var T {.inline.} =
  var i = 0
  while i < len(a):
    yield a[i]
    inc(i)

var lst = @[1, 2, 3]
for i in testI(lst):
  i += 1

echo lst
# @[2, 3, 4]

Это код, определенный в mitems для openArrays (уже импортированный в ваш код через system), поэтому приведенное выше можно переписать как:

var lst = @[1, 2, 3]
for i in mitems(lst):
  i += 1

echo lst
# @[2, 3, 4]
person xbello    schedule 26.08.2020