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

Javascript 函数式编程在这种情况下是否效率较低?,javascript,functional-programming,Javascript,Functional Programming,我正在读《Javascript中的函数式编程》一书 在第2章中,对于查找字符串中仅包含字母的前四个单词,命令式/函数式代码之间进行了以下比较: 迫切的 var words=[],count=0; text=myString.split(“”); 对于(i=0;count,在某些情况下(如您的情况)是的,但并非总是如此。许多函数式语言,如Haskell或Scala,都内置了惰性。这意味着函数不会立即求值,而是只在需要时才求值 如果您熟悉Java8,那么他们的Streams API也是惰性的,这意味

我正在读《Javascript中的函数式编程》一书

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

迫切的
var words=[],count=0;
text=myString.split(“”);
对于(i=0;count,在某些情况下(如您的情况)是的,但并非总是如此。许多函数式语言,如Haskell或Scala,都内置了惰性。这意味着函数不会立即求值,而是只在需要时才求值

如果您熟悉Java8,那么他们的Streams API也是惰性的,这意味着类似这样的东西不会遍历整个流3次

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

这是一个非常有趣的概念,您可以在这里查看Scala Stream类的文档

作为教程的一部分,对这两个代码片段进行比较非常有意义。函数编程要求很高,如果作者没有向读者介绍最有效的函数实现,那么例子很简单

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

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

  • 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

  • CPS启用了
    过滤器的纯变体

    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"]
    
    constfoldl=rf=>acc=>xs=>xs.length
    ?rf(acc)(xs[0])(acc=>foldL(rf)(acc)(xs.切片(1)))
    :acc;
    常数filterN=pred=>n=>foldL(
    acc=>x=>cont=>pred(x)
    ?附件长度+1!x.match(/[0-9]/);
    设xs=[“1”、“a”、“b”、“2”、“c”、“d”、“3”、“e”];
    过滤器(alpha)(4)(xs);//[“a”、“b”、“c”、“d”]
    
    foldR
    foldL
    的区别有点令人困惑。区别不在于可交换性,而在于关联性。CPS实现仍然有一个缺点。
    filterN
    应该分为
    filter
    take
    ,以提高代码重用性

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

    结论


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

    这是一个非常奇妙的问题。我的tentative的答案是“这取决于编译器/语言。”我知道Haskell做了一些疯狂的优化,因为它可以对很多行为做出完美的保证。对于Javascript来说,情况并非如此。看看惰性评估。不管它是否正确,请注意效率不是函数式编程的要点。还有其他更重要的功能,通常情况下,你是这样的甚至愿意用它们来换取执行速度。问题(或者至少你的推理)其实不是函数与命令式编程的问题。而是你为两者选择的解决方案。你可以有一种函数式方法,在找到前4项后也会停止。还有,“函数式”示例虽然可读性强且清晰,但并不是性能函数代码的最佳示例。该操作归结为一个简单的
    reduce
    ,我不认为您解释了“懒惰”的概念正确。Java streams示例中的情况是,
    过滤器
    操作是**交错的**-元素“流”一次一个元素通过三个过滤器的管道。这与惰性不同,惰性意味着永远不会访问不可能对结果流有贡献的基本元素。例如
    限制(long)
    Java 8 streams中的操作表现出惰性,基本流中超过限制的元素将永远不会被要求。你说得很好!我只是想演示如何优化API,使其与命令式构造一样具有性能,这是我想到的第一件事。下面是一个示例性实现属于