JavaScript 中的范围式迭代器
Range-style iterators in JavaScript
我希望提高我的 JavaScript 函数式编程技能。作为练习,我想移植到 JS H. S. Teoh 著名的无循环日历打印输出程序 - 此处由 Eric Niebler 在 C++ 端口中解释 https://wiki.dlang.org/Component_programming_with_ranges in its original D implementation, and in a fine YouTube lecture https://youtu.be/mFUXNMfaciE。
我曾尝试使用 JavaScript 的原生迭代器和生成器 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators,但很快就成功了。我的印象是这些不足以完成这项任务。特别是:
- 他们不允许对迭代器的非破坏性查询
完成状态,
- 没有克隆迭代器的机制。
我快速浏览了 RxJs http://reactivex.io/rxjs/ 以期将其用于此目的。我担心这需要大量的学习,而且过于矫枉过正,并不是真正设计来解决我遇到的问题(这不是异步问题等),据我所知可能行不通。
我的问题是:
- RxJs 是在 JS 中模拟范围式迭代器的合理方法吗?
- 如果上面 1. 的答案是 'no',什么是更好的库或方法?
JavaScript支持first-class数据功能,让您轻松抽象自己的玩具。下面我们发明了我们自己的 persistent (immutable) iterators using abstractions of our making, Yield
and Return
. Memoization 用于避免重复计算迭代器的下一个值。
我们的持久迭代器的行为与原生 JS 迭代器几乎相同,只是它们是不可变的! done
、value
和 next
属性应该很熟悉。
const Memo = (f, memo) => () =>
memo === undefined
? (memo = f (), memo)
: memo
const Yield = (value, next = Return) =>
({ done: false, value, next: Memo (next) })
const Return = value =>
({ done: true, value })
const Range = (min = 0, max = Infinity) =>
min > max
? Return ()
: Yield (min, () => Range (min + 1, max))
const state0 =
Range (0, 2)
console.log (state0.done) // false
console.log (state0.value) // 0
console.log (state0.value) // 0
const state1 =
state0.next ()
console.log (state1.done) // false
console.log (state1.value) // 1
console.log (state1.value) // 1
const state2 =
state1.next ()
console.log (state2.done) // false
console.log (state2.value) // 2
console.log (state2.value) // 2
const state3 =
state2.next ()
console.log (state3.done) // true
但您并不打算使用赋值遍历迭代器,递归正是我们在这里需要的
const MappedIterator = (f, it = Return ()) =>
it.done
? Return ()
: Yield (f (it.value), () => MappedIterator (f, it.next ()))
const Generator = function* (it = Return ())
{
while (it.done === false)
(yield it.value, it = it.next ())
return it.value
}
const square = x =>
x * x
Array.from (Generator (MappedIterator (square, Range (0, 10))))
// => [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]
当然,因为迭代器是持久的,我们可以多次单步执行同一个迭代器
const ConcatIterator = (x = Return (), y = Return ()) =>
x.done
? y
: Yield (x.value, () => ConcatIterator (x.next (), y))
const it =
MappedIterator (square, Range (1, 3))
Array.from (Generator (it)) // => [ 1, 4, 9 ]
Array.from (Generator (ConcatIterator (it, it))) // => [ 1, 4, 9, 1, 4, 9 ]
这是完整的代码演示
const Memo = (f, memo) => () =>
memo === undefined
? (memo = f (), memo)
: memo
const Yield = (value, next = Return) =>
({ done: false, value, next: Memo (next) })
const Return = value =>
({ done: true, value })
const Range = (min = 0, max = Infinity) =>
min > max
? Return ()
: Yield (min, () => Range (min + 1, max))
const MappedIterator = (f, it = Return ()) =>
it.done
? Return ()
: Yield (f (it.value), () => MappedIterator (f, it.next ()))
const ConcatIterator = (x = Return (), y = Return ()) =>
x.done
? y
: Yield (x.value, () => ConcatIterator (x.next (), y))
const Generator = function* (it = Return ())
{
while (it.done === false)
(yield it.value, it = it.next ())
return it.value
}
const it =
MappedIterator (x => x * x, Range (1, 3))
console.log (Array.from (Generator (it)))
// [ 1, 4, 9 ]
console.log (Array.from (Generator (ConcatIterator (it, it))))
// [ 1, 4, 9, 1, 4, 9 ]
我希望提高我的 JavaScript 函数式编程技能。作为练习,我想移植到 JS H. S. Teoh 著名的无循环日历打印输出程序 - 此处由 Eric Niebler 在 C++ 端口中解释 https://wiki.dlang.org/Component_programming_with_ranges in its original D implementation, and in a fine YouTube lecture https://youtu.be/mFUXNMfaciE。
我曾尝试使用 JavaScript 的原生迭代器和生成器 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators,但很快就成功了。我的印象是这些不足以完成这项任务。特别是:
- 他们不允许对迭代器的非破坏性查询 完成状态,
- 没有克隆迭代器的机制。
我快速浏览了 RxJs http://reactivex.io/rxjs/ 以期将其用于此目的。我担心这需要大量的学习,而且过于矫枉过正,并不是真正设计来解决我遇到的问题(这不是异步问题等),据我所知可能行不通。
我的问题是:
- RxJs 是在 JS 中模拟范围式迭代器的合理方法吗?
- 如果上面 1. 的答案是 'no',什么是更好的库或方法?
JavaScript支持first-class数据功能,让您轻松抽象自己的玩具。下面我们发明了我们自己的 persistent (immutable) iterators using abstractions of our making, Yield
and Return
. Memoization 用于避免重复计算迭代器的下一个值。
我们的持久迭代器的行为与原生 JS 迭代器几乎相同,只是它们是不可变的! done
、value
和 next
属性应该很熟悉。
const Memo = (f, memo) => () =>
memo === undefined
? (memo = f (), memo)
: memo
const Yield = (value, next = Return) =>
({ done: false, value, next: Memo (next) })
const Return = value =>
({ done: true, value })
const Range = (min = 0, max = Infinity) =>
min > max
? Return ()
: Yield (min, () => Range (min + 1, max))
const state0 =
Range (0, 2)
console.log (state0.done) // false
console.log (state0.value) // 0
console.log (state0.value) // 0
const state1 =
state0.next ()
console.log (state1.done) // false
console.log (state1.value) // 1
console.log (state1.value) // 1
const state2 =
state1.next ()
console.log (state2.done) // false
console.log (state2.value) // 2
console.log (state2.value) // 2
const state3 =
state2.next ()
console.log (state3.done) // true
但您并不打算使用赋值遍历迭代器,递归正是我们在这里需要的
const MappedIterator = (f, it = Return ()) =>
it.done
? Return ()
: Yield (f (it.value), () => MappedIterator (f, it.next ()))
const Generator = function* (it = Return ())
{
while (it.done === false)
(yield it.value, it = it.next ())
return it.value
}
const square = x =>
x * x
Array.from (Generator (MappedIterator (square, Range (0, 10))))
// => [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]
当然,因为迭代器是持久的,我们可以多次单步执行同一个迭代器
const ConcatIterator = (x = Return (), y = Return ()) =>
x.done
? y
: Yield (x.value, () => ConcatIterator (x.next (), y))
const it =
MappedIterator (square, Range (1, 3))
Array.from (Generator (it)) // => [ 1, 4, 9 ]
Array.from (Generator (ConcatIterator (it, it))) // => [ 1, 4, 9, 1, 4, 9 ]
这是完整的代码演示
const Memo = (f, memo) => () =>
memo === undefined
? (memo = f (), memo)
: memo
const Yield = (value, next = Return) =>
({ done: false, value, next: Memo (next) })
const Return = value =>
({ done: true, value })
const Range = (min = 0, max = Infinity) =>
min > max
? Return ()
: Yield (min, () => Range (min + 1, max))
const MappedIterator = (f, it = Return ()) =>
it.done
? Return ()
: Yield (f (it.value), () => MappedIterator (f, it.next ()))
const ConcatIterator = (x = Return (), y = Return ()) =>
x.done
? y
: Yield (x.value, () => ConcatIterator (x.next (), y))
const Generator = function* (it = Return ())
{
while (it.done === false)
(yield it.value, it = it.next ())
return it.value
}
const it =
MappedIterator (x => x * x, Range (1, 3))
console.log (Array.from (Generator (it)))
// [ 1, 4, 9 ]
console.log (Array.from (Generator (ConcatIterator (it, it))))
// [ 1, 4, 9, 1, 4, 9 ]