Mik*_*378 3 functional-programming scala variable-assignment concurrent-programming
在许多网络文章中,函数式编程被表示为避免各种变量重新分配,因此仅推广"最终"变量,至少为了更好的阅读.
它们中的大多数都采用带有计数器变量递增的不良循环的样本.(就像着名的i++
或者x = x + 1
.这里有一篇鲍勃叔叔的文章说明:FP第1集
因此,这些文章表明,依靠可变变量经常导致副作用,特别是防止我们所谓的"参照透明度",因此,更难以构建在多线程或更好的多处理器上运行的程序.
我的问题是:众所周知,i++
通常是一个线程LOCAL变量,因此即使并发处理也不会出现问题.
为什么选择一个像带有局部变量的循环这样的例子作为赋值的缺点,并且允许直接得出结论并发编程存在风险?这两件事与我严格无关.
为了更清楚,为什么不选择全局变量(或字段对象)的重新分配,这显然enemy
是并发编程,而不像Java那样过度使用所有的锁定模板.
我真的认为这个循环示例并不是将函数式编程的好处传递给命令式程序员的最好例证.
此外,它导致与"noob"函数程序员混淆,因为Scala例如在List
.scala类中使用了很多while循环模式:
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
}
Run Code Online (Sandbox Code Playgroud)
我认为Odersky自己说他们的目标是使API能够正常运行,但内部代码是特定实现的最佳选择.因此,您可能不应该搜索Scala库内部以"充分利用Scala"或"FP的优秀示例".
使用可变状态来保存索引(例如)也非常容易出错.因此,您应该将操作用于整个集合(filter/map/flatMap等),这样您就不必担心"索引越界"等问题.尽管如此,这些操作通常会导致大量的临时/中间集合被创建,因此它们会导致额外的垃圾收集.这通常对99%的程序无关紧要,但同样,这些程序在Scala库内部尽可能优化.
所以,是的,除了实现"生存"以及尽可能少的可变状态之外,还有一个地方可用于单线程程序,因为错误的可能性较小,可测试性更强,可读性更好.
在一个简单的循环,没有任何问题-这是不是有史以来并发的问题,你可能可以跟踪变量.也许.
// Take the first n items that pass p
def takeGood(n: Int)(p: A => Boolean): List[A] = {
val b = new ListBuffer[A]
var these = this
var i = 0
while (!these.isEmpty && i < n) {
i += 1
if (p(these.head)) b += these.head
these = these.tail
}
b.toList
}
Run Code Online (Sandbox Code Playgroud)
嗯,除了这不起作用 - 我们i
在每个循环上增加,而不仅仅是我们采取的循环.
如果你使用递归,至少,你正在做的事情变得更加明显:
def takeGood[A](these: List[A], n: Int)(p: A => Boolean)(b: ListBuffer[A] = new ListBuffer[A]): List[A] = {
if (these.isEmpty || n <= 0) b.toList
else if (p(these.head)) takeGood(these.tail, n-1)(p)({ b += these.head; b })
else takeGood(these.tail, n)(p)(b)
}
Run Code Online (Sandbox Code Playgroud)
因此,即使没有并发性,使用函数式风格也会有优势:有时(尤其是循环)它使循环更加明确,从而减少了出错的可能性.
并发性带来了额外的优势,因为它们一致但过时通常比不一致或死锁更好.但这不是迭代器的while循环中显示的内容.
归档时间: |
|
查看次数: |
996 次 |
最近记录: |