我正在阅读“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的任何情况,命令式版本会更快,因为它只运行到找到匹配的前四个单词标准,而功能版本首先过滤整个数组,然后将前四个元素分开。
我的问题是,我是否正确地假设这个?
答案 0 :(得分:6)
在某些情况下(和你一样)是的,但并非总是如此。很多像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
答案 1 :(得分:3)
这两个代码片段的比较非常有意义 - 作为教程的一部分。函数式编程要求很高,如果作者没有用最有效的函数实现来面对他的读者,那么为了简化示例。
为什么函数式编程要求很高?因为它遵循数学原则(而这些原则并不总是人类的逻辑),因为新手习惯于习惯性的风格。在FP中,数据流具有优先权,而实际算法保留在后台。习惯这种风格需要时间,但如果你曾经做过一次,你可能永远都不会回头!
如何以功能方式更有效地实现此示例?有几种可能性,我举两个例子。请注意,两种实现都避免使用中间数组:
严格评估Javascript。但是,可以使用thunks(nullary函数)模拟延迟评估。此外,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支持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"]
foldR
和foldL
的区别有点令人困惑。差异不在于交换性,而在于相关性。 CPS实施仍然存在缺陷。 filterN
应分为filter
和takeN
,以提高代码的可重用性。
传感器允许编写(减少/转换)函数,而不必依赖中间数组。因此,我们可以将filterN
分为两个不同的函数filter
和takeN
,从而提高其可重用性。遗憾的是,我还没有找到适合可理解和可执行示例的传感器的简洁实现。我将尝试开发自己的简化换能器解决方案,然后在此给出一个合适的例子。
<强>结论强>
如您所见,这些实现可能不如命令式解决方案有效。 Bergi已经指出,执行速度并不是函数式编程最关注的问题。如果微观优化对你很重要,你应该继续依赖命令式的风格。