发生器功能在功能编程中有效吗?


17

问题是:

  • 生成器是否破坏了功能编程范式?为什么或者为什么不?
  • 如果可以,可以在函数式编程中使用生成器吗?如何使用?

考虑以下:

function * downCounter(maxValue) {
  yield maxValue;
  yield * downCounter(maxValue > 0 ? maxValue - 1 : 0);
}

let counter = downCounter(26);
counter.next().value; // 26
counter.next().value; // 25
// ...etc

downCounter方法显示为无状态。同样,downCounter使用相同的输入进行调用将始终导致相同的输出。但是,同时,调用next()不会产生一致的结果。

我不确定生成器是否会破坏函数式编程范式,因为在此示例中counter是生成器对象,因此调用next()将产生与使用完全相同的另一个生成器对象产生的结果相同的结果maxValue

同样,调用someCollection[3]数组将始终返回第四个元素。同样,next()在生成器对象上调用四次也会始终返回第四个元素。

为了获得更多的上下文,在编写kata编程时提出了这些问题。回答问题的人提出了一个问题,即生成器是否可以在函数式编程中使用以及它们是否保持状态。


2
每个程序都保持状态。真正的问题是它是否符合功能状态的要求,我将其解释为“不可变状态”,即分配后不会改变的状态。我声称,使生成器在每次调用中返回不同内容的唯一方法是,是否以某种方式涉及了可变状态。
罗伯特·哈维

Answers:


14

生成器功能不是特别特殊。我们可以通过基于回调的样式重写generator函数来自己实现类似的机制:

function downCounter(maxValue) {
  return {
    "value": maxValue,
    "next": function () {
      return downCounter(maxValue > 0 ? maxValue - 1 : 0);
     },
  };
}

let counter = downCounter(26);
counter.value; //=> 26
counter.next().value; //=> 25

显然,它downCounter是一样的纯粹和功能。这里没有问题。

JavaScript使用的生成器协议涉及一个可变对象。这不是必需的,请参见上面的代码。尤其是,可变对象意味着我们失去了参照透明性 -用表达式的值替换表达式的能力。虽然在我的例子中,counter.next().value始终评估为25不管它在哪里发生的时间和频率,我们再说一遍,这是不是与JS发生器的情况下-在一个点它26,那么25,它真的可以是任何数字。如果我们将对生成器的引用传递给另一个函数,则会出现问题:

counter.next().value; //=> 25
otherFunction(counter); // does this consume the counter?
counter.next().value; // what will this be? It depends on the otherFunction()

很明显,生成器保持状态,因此不适合“纯”功能编程。幸运的是,您不必做纯函数式编程,而可能是实用的。如果生成器使您的代码更清晰,则应良心使用它们。毕竟,JavaScript不是纯粹的功能语言,与Haskell不同。

顺便说一下,在Haskell中,返回列表和生成器之间没有区别,因为它使用了惰性求值:

downCounter :: Int -> [Int]
downCounter maxValue =
  maxValue : (downCounter (max 0 (maxValue - 1)))
-- invoke as "take n (downCounter 26)" to display n elements
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.