#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
является универсальным, то есть работает с итераторами любого типа данных.
Может кто-нибудь, пожалуйста, объяснить мне, почему собственные итераторы отслеживают состояние? Почему разработчики языка ненавидят функциональное программирование?