在这种情况下,函数式编程的效率是否较低

Is functional programming less efficient for this case?

本文关键字:效率 是否 编程 这种情况下 函数      更新时间:2023-09-26

我正在阅读"Javascript中的函数式编程"一书。

在第 2 章中,对命令性/功能代码进行了以下比较,用于查找字符串中仅包含字母的前四个单词:

祈使的

var words = [], count = 0;
text = myString.split(' ');
for (i=0; count<4, i<text.length; i++) {
  if (!text[i].match(/[0-9]/)) {
    words = words.concat(text[i]);
    count++;
  }
}

功能的

var words = [];
var words = myString.split(' ').filter(function(x){
    return (! x.match(/[1-9]+/));
}).slice(0,4);

我推断,对于任何text长度大于 4 的情况,命令式版本都会更快,因为它只运行到找到符合条件的前四个单词,而函数版本首先过滤整个数组,然后才切开前四个元素。

我的问题是,我假设这个对吗?

在某些情况下(如您的(是的,但并非总是如此。许多函数式语言,如Haskell或Scala,都内置了懒惰。这意味着函数不会立即评估,而只会在需要时进行评估。

如果你熟悉Java 8,他们的Streams API也是懒惰的,这意味着这样的东西,不会遍历整个流3次。

stream.filter(n -> n < 200)
    .filter(n -> n % 2 == 0)
    .filter(n -> n > 15);

这是一个非常有趣的概念,你可以在这里查看Scala Stream类的文档 http://www.scala-lang.org/api/2.10.0/index.html#scala.collection.immutable.Stream

作为教程的一部分,这两个代码片段的比较非常有意义。函数式编程要求很高,如果作者没有用最有效的函数式实现来面对他的读者,那么保持示例简单。

为什么函数式编程要求很高?因为它遵循数学原理(这些并不总是人类逻辑(,并且因为新手习惯于定期使用命令式风格。在FP中,数据流具有优先级,而实际算法保留在后台。习惯这种风格需要时间,但如果你做过一次,你可能永远不会回头!

如何以功能方式更有效地实现此示例?有几种可能性,其中我说明两种。请注意,这两种实现都避免了中间数组:

  1. 惰性求值

Javascript是严格评估的。但是,惰性求值可以使用 thunks(空函数(来模拟。此外,需要foldR(右折叠(作为推导filterN的迭代函数:

const foldR = rf => acc => xs => xs.length
 ? rf(xs[0])(() => foldR(rf)(acc)(xs.slice(1)))
 : acc;
const filterN = pred => n => foldR(
  x => acc => pred(x) && --n ? [x].concat(acc()) : n ? acc() : [x]
)([]);
const alpha = x => !x.match(/[0-9]/);
let xs = ["1", "a", "b", "2", "c", "d", "3", "e"];
filterN(alpha)(4)(xs); // ["a", "b", "c", "d"]

此实现的缺点是filterN不是纯的,因为它是有状态的(n(。

  1. 延续传递样式

CPS支持filterN的纯变体:

const foldL = rf => acc => xs => xs.length
 ? rf(acc)(xs[0])(acc_ => foldL(rf)(acc_)(xs.slice(1)))
 : acc;
const filterN = pred => n => foldL(
  acc => x => cont => pred(x)
   ? acc.length + 1 < n ? cont(acc.concat(x)) : acc.concat(x)
   : cont(acc)
)([]);
const alpha = x => !x.match(/[0-9]/);
let xs = ["1", "a", "b", "2", "c", "d", "3", "e"];
filterN(alpha)(4)(xs); // ["a", "b", "c", "d"]

foldRfoldL有何不同有点令人困惑。区别不在于交换性,而在于结合性。CPS实现仍然有一个缺点。 filterN应分为filtertakeN,以增加代码的可重用性。

  1. 传感器

换能器允许组成(减少/变换(功能,而不必依赖中间阵列。因此,我们可以将filterN分为两个不同的函数filtertakeN,从而提高它们的可重用性。不幸的是,我还没有找到适合可理解和可执行示例的换能器的简洁实现。我将尝试开发我自己的简化传感器解决方案,然后在这里给出一个适当的示例。

结论

如您所见,这些实现可能不如命令式解决方案高效。Bergi已经指出,执行速度并不是函数式编程最相关的问题。如果微优化对您很重要,您应该继续依赖命令式样式。