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

时间:2016-10-12 11:21:38

标签: javascript functional-programming iterator ecmascript-6 side-effects

作为一名功能性程序员,我希望保持主要代码免受副作用的影响,并将它们转移到应用程序的边缘。 ES2015 IteratorIteration Protocols是抽象特定馆藏的有前途的方法。但是,Iterator也是有状态的。如果我完全依赖不可变的Iterable,我还能避免副作用吗?

2 个答案:

答案 0 :(得分:2)

Iterator导致可观察到的突变

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

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

答案 1 :(得分: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是通用的,它适用于任何数据类型的迭代器。

任何人都可以解释一下为什么原生迭代器是有状态的吗?为什么语言设计师讨厌函数式编程?