x在 JavaScript 中循环时间的典型方法是:
x
for (var i = 0; i < x; i++) doStuff(i);
但我根本不想使用++运算符或有任何可变变量。那么在 ES6 中有没有办法以x另一种方式循环时间?我喜欢 Ruby 的机制:
++
x.times do |i| do_stuff(i) end
JavaScript/ES6 中有类似的东西吗?我可以作弊并制作自己的发电机:
function* times(x) { for (var i = 0; i < x; i++) yield i; } for (var i of times(5)) { console.log(i); }
当然我还在用i++。至少它是看不见的 :),但我希望 ES6 中有更好的机制。
i++
好的!
下面的代码是使用 ES6 语法编写的,但也可以很容易地用 ES5 甚至更少的语法编写。ES6 不需要 创建“循环 x 次的机制”
如果你不需要回调中的迭代器 ,这是最简单的实现
const times = x => f => { if (x > 0) { f() times (x - 1) (f) } } // use it times (3) (() => console.log('hi')) // or define intermediate functions for reuse let twice = times (2) // twice the power ! twice (() => console.log('double vision'))
如果您确实需要迭代器 ,您可以使用带有计数器参数的命名内部函数来为您进行迭代
const times = n => f => { let iter = i => { if (i === n) return f (i) iter (i + 1) } return iter (0) } times (3) (i => console.log(i, 'hi'))
如果您不喜欢学习更多内容,请停止阅读此处…
但是这些东西应该让人感到不舒服......
if
undefined
“没有更好的办法吗?”
有。让我们首先回顾一下我们最初的实现
// times :: Int -> (void -> void) -> void const times = x => f => { if (x > 0) { **f()** // has to be side-effecting function times (x - 1) (f) } }
当然,这很简单,但请注意我们只是调用f()它而不用它做任何事情。这确实限制了我们可以重复多次的函数类型。即使我们有可用的迭代器,f(i)也不是更通用。
f()
f(i)
如果我们从一种更好的函数重复程序开始呢?也许可以更好地利用输入和输出。
通用功能重复
// repeat :: forall a. Int -> (a -> a) -> a -> a const repeat = n => f => x => { if (n > 0) return repeat (n - 1) (f) (f (x)) else return x } // power :: Int -> Int -> Int const power = base => exp => { // repeat <exp> times, <base> * <x>, starting with 1 return repeat (exp) (x => base * x) (1) } console.log(power (2) (8)) // => 256
上面,我们定义了一个通用repeat函数,它接受一个额外的输入,用于启动单个函数的重复应用。
repeat
// repeat 3 times, the function f, starting with x ... var result = repeat (3) (f) (x) // is the same as ... var result = f(f(f(x)))
实施times_repeat
times
现在这很容易;几乎所有的工作都已经完成。
// repeat :: forall a. Int -> (a -> a) -> a -> a const repeat = n => f => x => { if (n > 0) return repeat (n - 1) (f) (f (x)) else return x } // times :: Int -> (Int -> Int) -> Int const times = n=> f=> repeat (n) (i => (f(i), i + 1)) (0) // use it times (3) (i => console.log(i, 'hi'))
由于我们的函数i作为输入并返回i + 1,因此它有效地作为我们f每次传递的迭代器。
i
i + 1
f
我们也修复了问题的项目符号列表
JavaScript 逗号运算符,
如果您无法看到最后一个示例是如何工作的,这取决于您对 JavaScript 最古老的战斧之一的认识;逗号运算符——简而言之,它从左到右计算表达式并 返回 最后一个计算表达式的值
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
在我们上面的例子中,我使用
(i => (f(i), i + 1))
这只是一种简洁的写作方式
(i => { f(i); return i + 1 })
尾调用优化
与递归实现一样性感,在这一点上推荐它们对我来说是不负责任的,因为我能想到的任何JavaScript VM都不支持适当的尾调用消除 - babel 用于转换它,但它一直处于“损坏;将重新实施”状态已超过一年。
repeat (1e6) (someFunc) (x) // => RangeError: Maximum call stack size exceeded
因此,我们应该重新审视我们的实现repeat以使其堆栈安全。
下面的代码 确实 使用了可变变量n,x但请注意所有突变都本地化到repeat函数 - 从函数外部看不到任何状态更改(突变)
n
// repeat :: Int -> (a -> a) -> (a -> a) const repeat = n => f => x => { let m = 0, acc = x while (m < n) (m = m + 1, acc = f (acc)) return acc } // inc :: Int -> Int const inc = x => x + 1 console.log (repeat (1e8) (inc) (0)) // 100000000
这会让很多人说“但这不起作用!” ——我知道,放轻松。 我们可以使用纯表达式 为常量空间循环实现 Clojure 风格loop/接口;没有那些东西。recur __while
loop
recur
while
在这里,我们while用我们的loop函数进行抽象——它寻找一种特殊的recur类型来保持循环运行。当recur遇到非类型时,循环结束并返回计算结果
const recur = (...args) => ({ type: recur, args }) const loop = f => { let acc = f () while (acc.type === recur) acc = f (...acc.args) return acc } const repeat = $n => f => x => loop ((n = $n, acc = x) => n === 0 ? acc : recur (n - 1, f (acc))) const inc = x => x + 1 const fibonacci = $n => loop ((n = $n, a = 0, b = 1) => n === 0 ? a : recur (n - 1, b, a + b)) console.log (repeat (1e7) (inc) (0)) // 10000000 console.log (fibonacci (100)) // 354224848179262000000