这是一个例子-
// a terminating generator
const range = function* (from, to)
{ while (from < to)
yield from++
}
// higher-order generator
const G =
range(0, 100).filter(isEven).map(square)
for (const x of G)
console.log(x)
// (0*0) (2*2) (4*4) (6*6) (8*8) ...
// 0 4 16 36 64 ...
我们可以通过扩展生成器原型使这样的事情成为可能 -
const Generator =
Object.getPrototypeOf(function* () {})
Generator.prototype.map = function* (f, context)
{ for (const x of this)
yield f.call(context, x)
}
Generator.prototype.filter = function* (f, context)
{ for (const x of this)
if (f.call(context, x))
yield x
}
展开下面的 sn-p 以在您的浏览器中验证我们的进度 -
const Generator =
Object.getPrototypeOf(function* () {})
Generator.prototype.map = function* (f, context)
{ for (const x of this)
yield f.call(context, x)
}
Generator.prototype.filter = function* (f, context)
{ for (const x of this)
if (f.call(context, x))
yield x
}
// example functions
const square = x =>
x * x
const isEven = x =>
(x & 1) === 0
// an terminating generator
const range = function* (from, to)
{ while (from < to)
yield from++
}
// higher-order generator
for (const x of range(0, 100).filter(isEven).map(square))
console.log(x)
// (0*0) (2*2) (4*4) (6*6) (8*8) ...
// 0 4 16 36 64 ...
继续前进,fold 或 collect 之类的东西假定流最终会终止,否则它无法返回值 -
Generator.prototype.fold = function (f, acc, context)
{ for (const x of this)
acc = f.call(context, acc, x)
return acc
}
const result =
range(0, 100) // <- a terminating stream
.filter(isEven)
.map(square)
.fold(add, 0) // <- assumes the generator terminates
console.log(result)
// 161700
如果非要折叠无限流,可以实现limit-
Generator.prototype.limit = function* (n)
{ for (const x of this)
if (n-- === 0)
break // <-- stop the stream
else
yield x
}
// an infinite generator
const range = function* (x = 0)
{ while (true)
yield x++
}
// fold an infinite stream using limit
const result =
range(0) // infinite stream, starting at 0
.limit(100) // limited to 100 values
.filter(isEven) // only pass even values
.map(square) // square each value
.fold(add, 0) // fold values together using add, starting at 0
console.log(result)
// 161700
展开下面的sn-p,在浏览器中验证结果-
const Generator =
Object.getPrototypeOf(function* () {})
Generator.prototype.map = function* (f, context)
{ for (const x of this)
yield f.call(context, x)
}
Generator.prototype.filter = function* (f, context)
{ for (const x of this)
if (f.call(context, x))
yield x
}
Generator.prototype.fold = function (f, acc, context)
{ for (const x of this)
acc = f.call(context, acc, x)
return acc
}
Generator.prototype.limit = function* (n)
{ for (const x of this)
if (n-- === 0)
break // <-- stop the stream
else
yield x
}
const square = x =>
x * x
const isEven = x =>
(x & 1) === 0
const add = (x, y) =>
x + y
// an infinite generator
const range = function* (x = 0)
{ while (true)
yield x++
}
// fold an infinite stream using limit
const result =
range(0) // starting at 0
.limit(100) // limited to 100 values
.filter(isEven) // only pass even values
.map(square) // square each value
.fold(add, 0) // fold values together using add, starting at 0
console.log(result)
// 161700
在上面,请注意将limit 的顺序更改为在 filter 表达式之后如何更改结果 -
const result =
range(0) // starting at 0
.filter(isEven) // only pass even values
.limit(100) // limited to 100 values
.map(square) // square each value
.fold(add, 0) // fold values together using add, starting at 0
console.log(result)
// 1313400
在第一个程序中-
- 从无限范围开始
(0, 1, 2, 3, 4, ...)
- 限制为 100 个值
(0, 1, 2, 3, 4, ...,97, 98, 99)
- 只传递偶数
(0, 2, 4, ...94, 96, 98)
- 平方每个值
(0, 4, 16, ..., 8836, 9216, 9604)
- 使用加法折叠值,从 0 开始,
(0 + 0 + 4 + 16 + ..., + 8836 + 9216 + 9604)
- 结果
161700
在第二个程序中-
- 从无限范围开始
(0, 1, 2, 3, 4, ...)
- 只传递偶数
(0, 2, 4, ...)
- 限制为 100 个值
(0, 2, 4, 6, 8, ...194, 196, 198)
- 对每个值求平方
(0, 4, 16, 36, 64, ..., 37636, 38416, 29304)
- 使用加法折叠值,从 0 开始,
(0 + 4 + 16 + 36 + 64 + ..., + 37636+ 38416 + 29304)
- 结果
1313400
最后我们实现了collect,与fold不同,它不需要初始累加器。相反,第一个值是从流中手动抽取并用作初始累加器。流被恢复,将每个值与前一个值折叠 -
Generator.prototype.collect = function (f, context)
{ let { value } = this.next()
for (const x of this)
value = f.call(context, value, x)
return value
}
const toList = (a, b) =>
[].concat(a, b)
range(0,100).map(square).collect(toList)
// [ 0, 1, 2, 3, ..., 97, 98, 99 ]
range(0,100).map(square).collect(add)
// 4950
并注意双重消费您的流! JavaScript 没有给我们持久的迭代器,所以一旦一个流被消费,你就不能可靠地调用流上的其他高阶函数-
// create a stream
const stream =
range(0)
.limit(100)
.filter(isEven)
.map(square)
console.log(stream.fold(add, 0)) // 161700
console.log(stream.fold(add, 0)) // 0 (stream already exhausted!)
// create another stream
const stream2 =
range(0)
.limit(100)
.filter(isEven)
.map(square)
console.log(stream2.fold(add, 0)) // 161700
console.log(stream2.fold(add, 0)) // 0 (stream2 exhausted!)
这很可能发生在您执行merge 之类的操作时 -
const r =
range (0)
r.merge(r, r).limit(3).fold(append, [])
// double consume! bug!
// [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ] ]
// expected:
// [ [ 0, 0, 0 ], [ 1, 1, 1 ], [ 2, 2, 2 ] ]
// fresh range(0) each time
range(0).merge(range(0), range(0)).limit(3).fold(append, [])
// correct:
// [ [ 0, 0, 0 ], [ 1, 1, 1 ], [ 2, 2, 2 ] ]
每次使用 fresh 生成器 (range(0)...) 可以避免问题 -
const stream =
range(0)
.merge
( range(0).filter(isEven)
, range(0).filter(x => !isEven(x))
, range(0).map(square)
)
.limit(10)
console.log ('natural + even + odd + squares = ?')
for (const [ a, b, c, d ] of stream)
console.log (`${ a } + ${ b } + ${ c } + ${ d } = ${ a + b + c + d }`)
// natural + even + odd + squares = ?
// 0 + 0 + 1 + 0 = 1
// 1 + 2 + 3 + 1 = 7
// 2 + 4 + 5 + 4 = 15
// 3 + 6 + 7 + 9 = 25
// 4 + 8 + 9 + 16 = 37
// 5 + 10 + 11 + 25 = 51
// 6 + 12 + 13 + 36 = 67
// 7 + 14 + 15 + 49 = 85
// 8 + 16 + 17 + 64 = 105
// 9 + 18 + 19 + 81 = 127
这是为我们的生成器使用参数的关键原因:它会让您考虑正确地重用它们。因此,与其将stream 定义为上面的const,我们的流应该始终是函数,即使是空函数-
// streams should be a function, even if they don't accept arguments
// guarantees a fresh iterator each time
const megaStream = (start = 0, limit = 1000) =>
range(start) // natural numbers
.merge
( range(start).filter(isEven) // evens
, range(start).filter(x => !isEven(x)) // odds
, range(start).map(square) // squares
)
.limit(limit)
const print = s =>
{ for (const x of s)
console.log(x)
}
print(megaStream(0).merge(megaStream(10, 3)))
// [ [ 0, 0, 1, 0 ], [ 10, 10, 11, 100 ] ]
// [ [ 1, 2, 3, 1 ], [ 11, 12, 13, 121 ] ]
// [ [ 2, 4, 5, 4 ], [ 12, 14, 15, 144 ] ]
print(megaStream(0).merge(megaStream(10), megaStream(100)).limit(5))
// [ [ 0, 0, 1, 0 ], [ 10, 10, 11, 100 ], [ 100, 100, 101, 10000 ] ]
// [ [ 1, 2, 3, 1 ], [ 11, 12, 13, 121 ], [ 101, 102, 103, 10201 ] ]
// [ [ 2, 4, 5, 4 ], [ 12, 14, 15, 144 ], [ 102, 104, 105, 10404 ] ]
// [ [ 3, 6, 7, 9 ], [ 13, 16, 17, 169 ], [ 103, 106, 107, 10609 ] ]
// [ [ 4, 8, 9, 16 ], [ 14, 18, 19, 196 ], [ 104, 108, 109, 10816 ] ]
我们可以将merge 实现为-
Generator.prototype.merge = function* (...streams)
{ let river = [ this ].concat(streams).map(s => [ s, s.next() ])
while (river.every(([ _, { done } ]) => done === false))
{ yield river.map(([ _, { value } ]) => value)
river = river.map(([ s, _ ]) => [ s, s.next() ])
}
}
展开下面的sn-p,在浏览器中验证结果-
const Generator =
Object.getPrototypeOf(function* () {})
Generator.prototype.map = function* (f, context)
{ for (const x of this)
yield f.call(context, x)
}
Generator.prototype.filter = function* (f, context)
{ for (const x of this)
if (f.call(context, x))
yield x
}
Generator.prototype.limit = function* (n)
{ for (const x of this)
if (n-- === 0)
break // <-- stop the stream
else
yield x
}
Generator.prototype.merge = function* (...streams)
{ let river = [ this ].concat(streams).map(s => [ s, s.next() ])
while (river.every(([ _, { done } ]) => done === false))
{ yield river.map(([ _, { value } ]) => value)
river = river.map(([ s, _ ]) => [ s, s.next() ])
}
}
const isEven = x =>
(x & 1) === 0
const square = x =>
x * x
const range = function* (x = 0)
{ while (true)
yield x++
}
// streams should be functions, even if they don't have parameters
const megaStream = (start = 0, limit = 1000) =>
range(start) // natural numbers
.merge
( range(start).filter(isEven) // evens
, range(start).filter(x => !isEven(x)) // odds
, range(start).map(square) // squares
)
.limit(limit)
// for demo only
const print = s =>
{ for (const x of s) console.log(x) }
print(megaStream(0).merge(megaStream(10, 3)))
// [ [ 0, 0, 1, 0 ], [ 10, 10, 11, 100 ] ]
// [ [ 1, 2, 3, 1 ], [ 11, 12, 13, 121 ] ]
// [ [ 2, 4, 5, 4 ], [ 12, 14, 15, 144 ] ]
print(megaStream(0).merge(megaStream(10), megaStream(100)).limit(5))
// [ [ 0, 0, 1, 0 ], [ 10, 10, 11, 100 ], [ 100, 100, 101, 10000 ] ]
// [ [ 1, 2, 3, 1 ], [ 11, 12, 13, 121 ], [ 101, 102, 103, 10201 ] ]
// [ [ 2, 4, 5, 4 ], [ 12, 14, 15, 144 ], [ 102, 104, 105, 10404 ] ]
// [ [ 3, 6, 7, 9 ], [ 13, 16, 17, 169 ], [ 103, 106, 107, 10609 ] ]
// [ [ 4, 8, 9, 16 ], [ 14, 18, 19, 196 ], [ 104, 108, 109, 10816 ] ]