ES2015 迭代器和迭代协议可以避免副作用吗?
Can side effects be avoided with ES2015 Iterators and the Iteration Protocols?
作为一名函数式程序员,我想让我的主要代码没有副作用,并将它们转移到应用程序的边缘。 ES2015 Iterator
s 和 Iteration Protocols
是一种很有前途的抽象特定集合的方法。但是,Iterator
s 也是有状态的。如果我完全依赖不可变 Iterable
s,我还能避免副作用吗?
Iterator
s 导致可观察到的突变
迭代器有一个基本要素 属性:它们通过作为中介将消费者与 Iterable
的生产者分离。从消费者的角度来看,数据源是抽象的。它可能是 Array
、Object
或 Map
。这对消费者来说是完全不透明的。现在迭代过程的控制权从生产者转移到了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
是通用的,它适用于任何数据类型的迭代器。
谁能解释一下为什么原生迭代器是有状态的?为什么语言设计者讨厌函数式编程?
作为一名函数式程序员,我想让我的主要代码没有副作用,并将它们转移到应用程序的边缘。 ES2015 Iterator
s 和 Iteration Protocols
是一种很有前途的抽象特定集合的方法。但是,Iterator
s 也是有状态的。如果我完全依赖不可变 Iterable
s,我还能避免副作用吗?
Iterator
s 导致可观察到的突变
迭代器有一个基本要素 属性:它们通过作为中介将消费者与 Iterable
的生产者分离。从消费者的角度来看,数据源是抽象的。它可能是 Array
、Object
或 Map
。这对消费者来说是完全不透明的。现在迭代过程的控制权从生产者转移到了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
是通用的,它适用于任何数据类型的迭代器。
谁能解释一下为什么原生迭代器是有状态的?为什么语言设计者讨厌函数式编程?