【问题标题】:functional programming efficiency vs imperative函数式编程效率与命令式
【发布时间】:2018-06-11 18:52:24
【问题描述】:

我是函数式编程的新手,我刚刚遇到了一些事情,想知道是否有办法解决这个问题。

假设我有

myArray = [
  { a : 1 }
  { a : 4 }
  { a : 5 }
  { a : 6 }
  { a : 7 }
  { a : 8 }
]

假设我需要对这个数据集进行统计操作,例如

const median = myArray[ Math.ceil( myArray.length / 2 ) ]['a'] // Math.ceil .. Side Effect?
const fiveOrMore = myArray.filter( value => value.a >= 5 )
const lessThanFive = myArray.filter( value => value.a < 5 )

一些随意的例子。 目前的问题是,随着我需要做的统计操作数量的增加,效率会降低。

使用命令式风格,我可以在 ONE for 循环中完成所有操作。这是我采用的函数式编程的错误方法还是函数式编程范式本身的权衡

【问题讨论】:

  • 你的意思是.a在第一行吗?
  • @Hassan 第一行?
  • 如中,而不是[a]
  • @Hassan 刚刚修复。
  • 在这些情况下应该始终问的第一个问题是:您的数据集有多大,这些有关系吗? “过早的优化是万恶之源”- Knuth。

标签: javascript functional-programming


【解决方案1】:

这当然性能较差。而性能命中您选择的风格的权衡。

有人可能会说这没什么大不了的,因为时间复杂度是O(n)the only difference is the constant。我会说确保你对你的应用程序进行性能测试。如果速度很慢 - 是时候优化某些代码块了。

过早的优化是邪恶的。在许多情况下,命令式代码的运行速度会比函数式代码更快或快得多,甚至快得多,这取决于您可能会或可能不会接受的情况。

此外,还有多种技术可以提高性能。您不一定要更改样式。比如说,在某些情况下memoization 可以大大提高函数的速度,同时保持代码的功能。跳出框框思考。

【讨论】:

  • 每当你用它来谈论函数式编程时,我都会澄清“副作用”。 “权衡”可能是更合适的术语。
  • 所以实际上,这是函数式编程带来的可读性和多样性与效率之间的权衡。而且从人们的说法来看,如果数据集很小,则可以忽略不计。
  • @Aaron “从人们的说法来看,如果数据集很小,则可以忽略不计” 对。这是一个例子。在 JavaScript 中,少于 10 个数组的数组使用较慢的 InsertionSort 算法进行排序;而较长的则使用更快的 QuickSort 进行排序。这是因为,我们可以根据包括输入大小在内的许多因素对性能进行完全不同的判断......
【解决方案2】:

使用函数式样式,您也可以一次性完成。只需有一个检查元素是否位于下方的函数,累加器将是一个结构,其中包含您要添加元素的两个列表。

如果您正在考虑通过一系列高阶函数传递一个列表,那么您可以使用Trancducers 来减少开销,它基本上像单独的mapfilter 一样工作,但在操作之间没有列表。

如果您可能不会使用结果中的所有元素,则有 streams 采用惰性评估。

还有generators。基本上你可以创建几个for 循环并使用ỳield 来“返回”一个值,你可以将它们链接起来,因为所有生成器都可以使用for of 进行迭代。同样在这里,只要您有足够的数据,您就可以暂停。

因此,对于所有这些而言,各有利弊。如果您要使用生成器和流来计算所有元素,则性能方面会有一些开销。换能器可能是更好的选择,它可以通过很少的列表制作来提供可组合性,但循环当然会更快。

使用功能实现可以更轻松地进行测试,并且您可以单独测试各个阶段。统治整个应用程序的一个非常大的循环通常很难调试。当您有一个 reduce 时,也会出现这种情况,它只是以功能样式重写一个循环。

【讨论】:

【解决方案3】:

您可以以函数式或命令式的方式将内容塞进一个循环中。在 both 样式中,它会损害可读性。在 both 样式中,您的编译器可以使用loop fusion 来消除额外的循环。此外,单个循环并不总是更快,编译器将能够比人类更容易地识别适合融合的情况。

正如 Sylwester 所指出的,函数式编程有许多技术可以让您分别编写循环但一起执行它们。函数式风格也更容易显式并行化到多个线程上。

通常还有一些库函数已经在单个循环中完成了您想要的操作,并且更具描述性和简洁性。例如,您的最后两行可以使用partition

_.partition(myArray, x => x['a'] < 5)

【讨论】:

    【解决方案4】:

    我的座右铭之一是:

    “我不在乎我能以多快的速度计算出错误的答案。”

    我可能会尝试编写一些超快速的代码,但如果我的代码难以理解,因此很容易引入错误/难以发现错误,这是有风险的。除非代码对执行速度非常关键,否则我会选择最容易表达我想要做的事情的方法:有时函数式方法可以使您的意图非常清晰,而在其他情况下,命令式更合适。

    一旦我的代码可以工作,并通过了一组单元测试,我就可以在需要时重写以寻求更快的速度。

    顺便说一句,这就是我目前喜欢 Swift 的原因 - 我有可用的函数式和命令式选项。哦,我知道它不是纯功能性的,但是在我的工程而不是计算机科学方面,它已经足够接近了! ;-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-20
      • 2014-03-22
      • 1970-01-01
      • 1970-01-01
      • 2015-12-26
      • 2010-12-19
      相关资源
      最近更新 更多