ES2015 迭代器和迭代协议可以避免副作用吗?

Can side effects be avoided with ES2015 Iterators and the Iteration Protocols?

作为一名函数式程序员,我想让我的主要代码没有副作用,并将它们转移到应用程序的边缘。 ES2015 Iterators 和 Iteration Protocols 是一种很有前途的抽象特定集合的方法。但是,Iterators 也是有状态的。如果我完全依赖不可变 Iterables,我还能避免副作用吗?

Iterators 导致可观察到的突变

迭代器有一个基本要素 属性:它们通过作为中介将消费者与 Iterable 的生产者分离。从消费者的角度来看,数据源是抽象的。它可能是 ArrayObjectMap。这对消费者来说是完全不透明的。现在迭代过程的控制权从生产者转移到了Iterator,后者可以建立一个pull机制,消费者可以懒惰使用

要管理其任务,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 或至少谨慎使用它们。

纯迭代器非常简单。我们只需要

  • 当前值
  • 一个推进迭代器的闭包
  • 一种表示迭代器已耗尽的方法
  • 包含这些属性的适当数据结构

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 是通用的,它适用于任何数据类型的迭代器。

谁能解释一下为什么原生迭代器是有状态的?为什么语言设计者讨厌函数式编程?