Можно ли избежать побочных эффектов с помощью итераторов ES2015 и протоколов итерации?

#javascript #functional-programming #iterator #ecmascript-6 #side-effects

#javascript #функциональное программирование #итератор #ecmascript-6 #побочные эффекты

Вопрос:

Как функциональный программист, я хочу, чтобы мой основной код был свободен от побочных эффектов и переносил их на край приложения. ES2015 Iterator s и Iteration Protocols являются многообещающим способом абстрагирования конкретных коллекций. Однако Iterator s также отслеживают состояние. Могу ли я по-прежнему избегать побочных эффектов, если я полагаюсь исключительно на неизменяемые Iterable s?

Комментарии:

1. Если iterable является неизменяемым, какие побочные эффекты могут быть у итератора?

2. Это все еще многоадресная рассылка, то есть вы можете поделиться next эффектом.

Ответ №1:

Iterator s вызывают наблюдаемые мутации

Итераторы обладают одним существенным свойством: они отделяют потребителя от производителя Iterable , выступая в качестве посредника. С точки зрения потребителя источник данных абстрагируется. Это может быть an Array , an Object или a Map . Это совершенно непрозрачно для потребителя. Теперь, когда управление процессом итерации переходит от производителя к Iterator производителю, последний может установить механизм извлечения, который может лениво использоваться потребителем.

Для управления своей задачей an Iterator должен отслеживать состояние итерации. Следовательно, он должен быть с отслеживанием состояния. Само по себе это не вредно. Однако это становится вредным, как только наблюдаются изменения состояния:

 const xs = [1,2,3,4,5];

const foo = itor => Array.from(itor);

const itor = xs.keys();

console.log(itor.next()); // 0

// share the iterator

console.log(foo(itor)); // [2,3,4,5] => observed mutation

console.log(itor.next()) // {value: undefined, done: true} => observed mutation  

Эти эффекты возникают, даже если вы работаете только с неизменяемыми типами данных.

Как функциональный программист, вы должны избегать Iterator s или, по крайней мере, использовать их с большой осторожностью.

Комментарии:

1. Отличный вопрос с отличным ответом. Кроме того, что такое thunk?

2. » Итераторы, похоже, побуждают людей изменять итеративные объекты» — я этого не вижу. Спецификация просто требует, чтобы они не ломались в случае их изменения во время итерации, но это остается плохой практикой.

3. Да, никогда не следует делиться итераторами. Следует использовать iterable — возможность создания нового (локального, собственного) итератора путем вызова [Symbol.iterator]() — это весь смысл протокола итерации.

4. @Bergi Люди будут использовать протокол итерации по своему усмотрению. Я думаю, что хорошая языковая функция должна быть изначально безопасной — не только если вы соблюдаете определенные правила.

5. @Bergi «Спецификация просто требует, чтобы они не ломались» — вот что я имею в виду под «поощрением».

Ответ №2:

Чистый итератор очень прост. Все, что нам нужно, это

  • текущее значение
  • закрытие, которое продвигает итератор
  • способ сигнализировать о том, что итератор исчерпан
  • соответствующая структура данных, содержащая эти свойства

 const ArrayIterator = xs => {
  const aux = i => i in xs
    ? {value: xs[i], next: () => aux(i   1), done: false}
    : {done: true};

  return aux(0);
};

const take = n => ix => {
  const aux = ({value, next, done}, acc) =>
    done ? acc
      : acc.length === n ? acc
      : aux(next(), acc.concat(value));

  return aux(ix, []);
};

const ix = ArrayIterator([1,2,3,4,5]);

console.log(
  take(3) (ix));
  
console.log(
  ix.next().value,
  ix.next().value,
  ix.next().next().value)  

Нигде нет глобального состояния. Вы можете реализовать его для любого итеративного типа данных. take является универсальным, то есть работает с итераторами любого типа данных.

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