Pri*_*osK 15 functional-programming scala imperative-programming
来自Scala编程(第二版),第98页的底部:
对Scala程序员的平衡态度
首选vals,不可变对象和没有副作用的方法.首先到达他们.当您有特定的需求和理由时,请使用变量,可变对象和带副作用的方法.
在前几页解释了为什么更喜欢val,不可变对象和没有副作用的方法,所以这句话很有意义.
但第二句话:"当你有特殊的需要和理由时,使用变量,可变对象和副作用的方法." 没有这么好解释.
所以我的问题是:
什么是使用变量,可变对象和具有副作用的方法的理由或特定需要?
Ps:如果有人可以为每个人提供一些例子(除了解释),那将是很棒的.
Hei*_*ger 16
在许多情况下,函数式编程可以提高抽象级别,从而使您的代码更简洁,更容易/更快地编写和理解.但是有些情况下,生成的字节码不能像命令式解决方案那样优化(快速).
目前(Scala 2.9.1)一个很好的例子是总结范围:
(1 to 1000000).foldLeft(0)(_ + _)
Run Code Online (Sandbox Code Playgroud)
与:
var x = 1
var sum = 0
while (x <= 1000000) {
sum += x
x += 1
}
Run Code Online (Sandbox Code Playgroud)
如果您对这些进行分析,您会发现执行速度存在显着差异.因此,有时候表现是一个非常好的理由.
使用可变性的一个原因是,如果您正在跟踪一些正在进行的过程.例如,假设我正在编辑一个大文档,并且有一组复杂的类来跟踪文本的各种元素,编辑历史,光标位置等.现在假设用户点击文本的不同部分.我是否重新创建文档对象,复制许多字段而不是EditState字段; 重新创建EditState新的ViewBounds和documentCursorPosition?或者我在一个地方改变一个可变变量? 只要线程安全不是问题,那么只更新一个或两个变量比复制所有内容更简单且更不容易出错.如果线程安全是一个问题,那么防止并发访问可能比使用不可变方法和处理过期请求更多的工作.
使用可变性的另一个原因是速度.对象创建很便宜,但简单的方法调用更便宜,对原始类型的操作也更便宜.
例如,假设我们有一个地图,我们想要对值和值的平方求和.
val xs = List.range(1,10000).map(x => x.toString -> x).toMap
val sum = xs.values.sum
val sumsq = xs.values.map(x => x*x).sum
Run Code Online (Sandbox Code Playgroud)
如果你偶尔这样做,这没什么大不了的.但是如果你关注正在发生的事情,对于你首先重新创建它的每个列表元素(值),然后将它加总(加框),然后再次重新创建它(值),然后再用方块形式再次重新创建它(图)然后总结一下.这至少是六个对象创建和五个完整遍历,只需要每个项目进行两次加法和一次乘法. 难以置信的低效率.
您可以尝试通过避免多次递归并使用折叠仅传递一次地图来做得更好:
val (sum,sumsq) = ((0,0) /: xs){ case ((sum,sumsq),(_,v)) => (sum + v, sumsq + v*v) }
Run Code Online (Sandbox Code Playgroud)
这要好得多,我的机器性能提高了15倍.但是每次迭代你仍然有三个对象创建.如果相反你
case class SSq(var sum: Int = 0, var sumsq: Int = 0) {
def +=(i: Int) { sum += i; sumsq += i*i }
}
val ssq = SSq()
xs.foreach(x => ssq += x._2)
Run Code Online (Sandbox Code Playgroud)
你再次快两倍,因为你减少了拳击.如果您将数据放在数组中并使用while循环,那么您可以避免所有对象创建和装箱,并加速另一个因子20.
现在,也就是说,你也可以为你的数组选择一个递归函数:
val ar = Array.range(0,10000)
def suma(xs: Array[Int], start: Int = 0, sum: Int = 0, sumsq: Int = 0): (Int,Int) = {
if (start >= xs.length) (sum, sumsq)
else suma(xs, start+1, sum+xs(start), sumsq + xs(start)*xs(start))
}
Run Code Online (Sandbox Code Playgroud)
并且以这种方式编写它与可变SSq一样快.但如果我们这样做:
def sumb(xs: Array[Int], start: Int = 0, ssq: (Int,Int) = (0,0)): (Int,Int) = {
if (start >= xs.length) ssq
else sumb(xs, start+1, (ssq._1+xs(start), ssq._2 + xs(start)*xs(start)))
}
Run Code Online (Sandbox Code Playgroud)
我们现在再次慢了10倍,因为我们必须在每一步创建一个对象.
因此,最重要的是,当您无法方便地将更新结构作为方法的独立参数进行传递时,实际上唯一重要的是您具有不变性.一旦超越了工作的复杂性,可变性就是一个巨大的胜利.
如果需要使用n来自潜在故障数据的字段构建复杂对象,则可以使用如下所示的构建器模式:
abstract class Built {
def x: Int
def y: String
def z: Boolean
}
private class Building extends Built {
var x: Int = _
var y: String = _
var z: Boolean = _
}
def buildFromWhatever: Option[Built] = {
val b = new Building
b.x = something
if (thereIsAProblem) return None
b.y = somethingElse
// check
...
Some(b)
}
Run Code Online (Sandbox Code Playgroud)
这仅适用于可变数据.当然还有其他选择:
class Built(val x: Int = 0, val y: String = "", val z: Boolean = false) {}
def buildFromWhatever: Option[Built] = {
val b0 = new Built
val b1 = b0.copy(x = something)
if (thereIsAProblem) return None
...
Some(b)
}
Run Code Online (Sandbox Code Playgroud)
在许多方面它甚至更干净,除了你必须为你做的每一次改变复制你的对象,这可能会非常缓慢.这些都不是特别防弹的; 因为你可能想要的
class Built(val x: Int, val y: String, val z: Boolean) {}
class Building(
val x: Option[Int] = None, val y: Option[String] = None, val z: Option[Boolean] = None
) {
def build: Option[Built] = for (x0 <- x; y0 <- y; z0 <- z) yield new Built(x,y,z)
}
def buildFromWhatever: Option[Build] = {
val b0 = new Building
val b1 = b0.copy(x = somethingIfNotProblem)
...
bN.build
}
Run Code Online (Sandbox Code Playgroud)
但同样,还有很多开销.
| 归档时间: |
|
| 查看次数: |
1299 次 |
| 最近记录: |