【发布时间】:2012-12-24 12:55:31
【问题描述】:
在许多网络文章中,函数式编程被描述为避免所有类型的变量重新分配,因此至少只推广“最终”变量,以便更好地阅读。
它们中的大多数都取自一个计数器变量递增的不良循环的样本。 (如著名的i++ 或x = x + 1。
这是 Bob 叔叔的一篇文章说明它:FP Episode 1
因此,这些文章表明,依赖可变变量经常会导致副作用,尤其是会阻止我们所谓的“引用透明”,因此更难构建在多线程或更好的多处理器上运行的程序.
我的问题是:众所周知,i++ 通常是线程 LOCAL 变量,因此即使并发处理也不会出现问题。
为什么要选择像带有局部变量的循环这样的例子作为赋值的缺点,并允许直接得出并发编程有风险的结论??这两件事与我完全无关。 p>
为什么不呢,为了更清楚,选择一个全局变量(或字段对象)的重新分配,这显然是并发编程的enemy,而不像 Java 那样过度使用所有的锁样板。
我真的认为这个循环示例并不是将函数式编程的好处传递给命令式程序员的最佳例证。
此外,由于 Scala 使用了大量的 while 循环模式,例如 List.scala 类:
override def take(n: Int): List[A] = {
val b = new ListBuffer[A]
var i = 0
var these = this
while (!these.isEmpty && i < n) {
i += 1 // reassignment here
b += these.head
these = these.tail
}
if (these.isEmpty) this
else b.toList
}
【问题讨论】:
-
这就是为什么 scala 是一种混合语言 - 它使用最适合任务的任何东西,偏向于函数式风格。可变计数器的例子就像你说的 - 一个糟糕的例子。没有什么会使可变状态在任何情况下都变得不好(除非你是一个纯粹主义者,而且看起来他们毕竟是对的),只是很难划清区分“好的可变状态”和“坏的可变状态”的界限因此最好完全避免它。您制造的例外越少,就越容易理解您想要实现的目标。
-
这也可能不是关于 SO 的最佳问题 :)
-
我认为 Odersky 本人说过,他们的目标是使 API 具有功能性,但内部代码对于特定实现来说是最优化的。
-
@soulcheck,它并没有真正征求意见。我可能忽略的是多核编程的特殊情况。我目前正在搜索是否在任何特定情况下,两个处理器都可以共享局部变量......因此解释了循环样本及其可能涉及的风险。顺便说一句,我同意你的第一条评论:)
-
在循环中使用可变计数器会使循环并行化变得更加困难。如果您使用了
map或fold,我们可以将其替换为平行地图或折叠。
标签: scala functional-programming variable-assignment concurrent-programming