是否有一种机制可以在没有可变变量的情况下在 ES6(ECMAScript 6)中循环 x 次? [英] Is there a mechanism to loop x times in ES6 (ECMAScript 6) without mutable variables?

查看:18
本文介绍了是否有一种机制可以在没有可变变量的情况下在 ES6(ECMAScript 6)中循环 x 次?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 JavaScript 中循环 x 次的典型方法是:

The typical way to loop x times in JavaScript is:

for (var i = 0; i < x; i++)
  doStuff(i);

但我根本不想使用 ++ 运算符或有任何可变变量.那么,在 ES6 中,有没有办法以另一种方式循环 x 次?我喜欢 Ruby 的机制:

But I don't want to use the ++ operator or have any mutable variables at all. So is there a way, in ES6, to loop x times another way? I love Ruby's mechanism:

x.times do |i|
  do_stuff(i)
end

JavaScript/ES6 中是否有类似的东西?我可以作弊并制作自己的发电机:

Anything similar in JavaScript/ES6? I could kind of cheat and make my own generator:

function* times(x) {
  for (var i = 0; i < x; i++)
    yield i;
}

for (var i of times(5)) {
  console.log(i);
}

当然我仍在使用 i++.至少它不在视线范围内:),但我希望 ES6 中有更好的机制.

Of course I'm still using i++. At least it's out of sight :), but I'm hoping there's a better mechanism in ES6.

推荐答案

好的!

下面的代码是使用 ES6 语法编写的,但也可以很容易地使用 ES5 甚至更少的语法编写.ES6 不是创建循环 x 次的机制"

The code below is written using ES6 syntaxes but could just as easily be written in ES5 or even less. ES6 is not a requirement to create a "mechanism to loop x times"

如果不需要回调中的迭代器,这是最简单的实现

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'))

如果你确实需要迭代器,你可以使用一个带计数器参数的命名内部函数来为你迭代

If you do need the iterator, you can use a named inner function with a counter parameter to iterate for you

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'))

如果您不喜欢学习更多东西,请停止阅读......

Stop reading here if you don't like learning more things ...

但是这些东西应该让人觉得有些不对劲……

  • 单分支 if 语句很丑陋 —另一个分支会发生什么?
  • 函数体中的多个语句/表达式—程序问题是混合的吗?
  • 隐式返回undefined —指示不纯的副作用
  • single branch if statements are ugly — what happens on the other branch ?
  • multiple statements/expressions in the function bodies — are procedure concerns being mixed ?
  • implicitly returned undefined — indication of impure, side-effecting function

难道没有更好的方法吗?"

有.让我们首先回顾一下我们最初的实现

There is. Let's first revisit our initial implementation

// 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) 也不是通用的多.

Sure, it's simple, but notice how we just call f() and don't do anything with it. This really limits the type of function we can repeat multiple times. Even if we have the iterator available, f(i) isn't much more versatile.

如果我们从一种更好的函数重复程序开始呢?也许可以更好地利用输入和输出.

What if we start with a better kind of function repetition procedure ? Maybe something that makes better use of input and output.

泛型函数重复

// 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 函数,它接受一个额外的输入,用于启动单个函数的重复应用.

Above, we defined a generic repeat function which takes an additional input which is used to start the repeated application of a single function.

// 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)))

<小时>

使用repeat

现在这很容易;几乎所有的工作都已经完成.

Well this is easy now; almost all of the work is already done.

// 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 每个的迭代器时间.

Since our function takes i as an input and returns i + 1, this effectively works as our iterator which we pass to f each time.

我们也修复了问题的项目符号列表

We've fixed our bullet list of issues too

  • 不再有丑陋的单分支if语句
  • 单表达式主体表示很好分离的关注点
  • 不再无用,隐式返回undefined

JavaScript 逗号运算符,

如果您在查看最后一个示例的工作方式时遇到问题,这取决于您对 JavaScript 最古老的战斧之一的认识;逗号运算符——简而言之,它评估表达式从左到右,返回最后一个计算表达式的值

In case you're having trouble seeing how the last example is working, it depends on your awareness of one of JavaScript's oldest battle axes; the comma operator – in short, it evaluates expressions from left to right and returns the value of the last evaluated expression

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

在我们上面的例子中,我使用

In our above example, I'm using

(i => (f(i), i + 1))

这只是一种简洁的写作方式

which is just a succinct way of writing

(i => { f(i); return i + 1 })

<小时>

尾调用优化

与递归实现一样性感,鉴于没有 JavaScript VM 我可以想到支持适当的尾调用消除 - babel 用于转译它,但它已经处于损坏;将重新实现"状态超过一年.

As sexy as the recursive implementations are, at this point it would be irresponsible for me to recommend them given that no JavaScript VM I can think of supports proper tail call elimination – babel used to transpile it, but it's been in "broken; will reimplement" status for well over a year.

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

因此,我们应该重新审视我们的 repeat 实现以使其堆栈安全.

As such, we should revisit our implementation of repeat to make it stack-safe.

下面的代码确实使用可变变量nx,但请注意所有的变化都被定位到repeat> 函数——从函数外部看不到状态变化(突变)

The code below does use mutable variables n and x but note that all mutations are localized to the repeat function – no state changes (mutations) are visible from outside of the function

// 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 的东西.

This is going to have a lot of you saying "but that's not functional !" – I know, just relax. We can implement a Clojure-style loop/recur interface for constant-space looping using pure expressions; none of that while stuff.

这里我们用我们的 loop 函数抽象了 while——它寻找一个特殊的 recur 类型来保持循环运行.当遇到非recur类型时,循环结束,返回计算结果

Here we abstract while away with our loop function – it looks for a special recur type to keep the loop running. When a non-recur type is encountered, the loop is finished and the result of the computation is returned

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

这篇关于是否有一种机制可以在没有可变变量的情况下在 ES6(ECMAScript 6)中循环 x 次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆