为什么我应该避免在Scala中使用局部可修改变量?
我对Scala非常陌生,大多数时候在我使用Java之前。现在我的代码中到处都有警告,说我应该“避免可变的局部变量”,我有一个简单的问题——为什么 假设我有一个小问题——确定四个中的最大整数。我的第一个方法是:为什么我应该避免在Scala中使用局部可修改变量?,scala,functional-programming,immutability,Scala,Functional Programming,Immutability,我对Scala非常陌生,大多数时候在我使用Java之前。现在我的代码中到处都有警告,说我应该“避免可变的局部变量”,我有一个简单的问题——为什么 假设我有一个小问题——确定四个中的最大整数。我的第一个方法是: def max4(a: Int, b: Int,c: Int, d: Int): Int = { var subMax1 = a if (b > a) subMax1 = b var subMax2 = c if (d > c) subMax2 = d
def max4(a: Int, b: Int,c: Int, d: Int): Int = {
var subMax1 = a
if (b > a) subMax1 = b
var subMax2 = c
if (d > c) subMax2 = d
if (subMax1 > subMax2) subMax1
else subMax2
}
考虑到此警告消息后,我找到了另一个解决方案:
def max4(a: Int, b: Int,c: Int, d: Int): Int = {
max(max(a, b), max(c, d))
}
def max(a: Int, b: Int): Int = {
if (a > b) a
else b
}
看起来更漂亮,但这背后的意识形态是什么
每当我处理一个问题时,我都会这样想:“好吧,我们从这个开始,然后逐步改变,然后得到答案。”。我知道问题是我试图改变一些初始状态以得到答案,但不明白为什么至少在局部改变是不好的?那么,如何使用Scala之类的函数式语言在集合上进行迭代呢
举个例子:假设我们有一个整数列表,如何编写一个函数来返回可被6整除的整数子列表?无法想象没有局部可变变量的解决方案。在您的特定情况下,还有另一种解决方案:
def max4(a: Int, b: Int,c: Int, d: Int): Int = {
val submax1 = if (a > b) a else b
val submax2 = if (c > d) c else d
if (submax1 > submax2) submax1 else submax2
}
这不是更容易理解吗?当然,我有点偏见,但我倾向于这样认为,但不要盲目遵循这条规则。如果您看到某些代码可能以易变的风格编写得更容易、更简洁,那么就这样做吧——scala的巨大优势在于,您不需要承诺不可变或易变的方法,您可以在它们之间摇摆(顺便说一句,这同样适用于return
关键字用法)
例如:假设我们有一个int列表,如何编写
函数,该函数返回可被6整除的整数的子列表?
无法想到没有局部可变变量的解决方案
当然,使用递归编写这样的函数是可能的,但是,同样,如果可变的解决方案看起来很好并且工作正常,为什么不呢 一般来说,它与Scala的关系不如与函数式编程方法论的关系大。其思想如下:如果您有常量变量(Java中的final),那么您可以使用它们,而不用担心它们会改变。同样,您可以并行化代码,而不用担心竞争条件或线程不安全的代码 在您的示例中,这并不重要,但是请想象以下示例:
val variable = ...
new Future { function1(variable) }
new Future { function2(variable) }
使用最终变量可以确保不会出现任何问题。否则,您必须检查主线程以及function1和function2
当然,如果不改变可变变量,也有可能获得相同的结果。但是使用不可变的,你可以确定情况会是这样
编辑以回答您的编辑:
本地可变项并不坏,这就是您可以使用它们的原因。但是,如果您尝试在没有它们的情况下思考方法,您可以得到与您发布的解决方案相同的解决方案,该解决方案更干净,并且可以非常容易地并行化
那么,如何使用Scala之类的函数式语言在集合上进行迭代呢
您始终可以在不可更改的集合上进行迭代,而不需要更改任何内容。例如:
val list = Seq(1,2,3)
for (n <- list)
println n
for (int i=0; i<100; i++) {
for (int j=0; j<100; i++) {
System.out.println("i is " + i = " and j is " + j);
}
}
首先,您可以这样重写您的示例:
def max(first: Int, others: Int*): Int = {
val curMax = Math.max(first, others(0))
if (others.size == 1) curMax else max(curMax, others.tail : _*)
}
val data = numbers.foreach(_.map(a => doStuff(a).flatMap(somethingElse)).foldleft("", (a : Int,b: Int) => a + b))
这使用varargs和tail递归来查找最大数。当然,做同样的事情还有很多其他的方法
回答你的问题——这是一个很好的问题,也是我第一次开始使用scala时想到的一个问题。就我个人而言,我认为整个不可变/函数式编程方法有点夸大了。但对于它的价值,这里有支持它的主要论点:
不可变代码更易于阅读(主观)
不可变代码更健壮-改变可变状态会导致bug,这是事实。以此为例:
val list = Seq(1,2,3)
for (n <- list)
println n
for (int i=0; i<100; i++) {
for (int j=0; j<100; i++) {
System.out.println("i is " + i = " and j is " + j);
}
}
不可变的数据结构不允许您这样做,因此您需要明确地考虑如何处理它。当然,正如您所指出的,局部变量通常是线程安全的,但不能保证。例如,可以将ListBuffer实例变量作为参数传递给方法
但是,不可变和函数式编程风格也有缺点:
性能。它通常在编译和运行时都比较慢。编译器必须强制执行不变性,JVM必须分配比可变数据结构所需的更多的对象。这对于收藏品尤其如此
大多数scala示例显示类似于val numbers=List(1,2,3)
的内容,但在现实世界中,硬编码值很少。我们通常动态地(从数据库查询等)构建集合。虽然scala可以重新分配集合中的值,但每次修改它时,它仍必须创建一个新的集合对象。如果要向scala列表(不可变)添加1000个元素,JVM将需要分配(然后是GC)1000个对象
难以维护。函数代码可能很难阅读,看到这样的代码并不少见:
def max(first: Int, others: Int*): Int = {
val curMax = Math.max(first, others(0))
if (others.size == 1) curMax else max(curMax, others.tail : _*)
}
val data = numbers.foreach(_.map(a => doStuff(a).flatMap(somethingElse)).foldleft("", (a : Int,b: Int) => a + b))
我不知道你的情况,但我发现这种代码很难理解
难以调试。功能代码也很难调试。试着在我上面的(糟糕的)示例中添加一个断点
我的建议是使用一种功能性的/不变的风格,这种风格真正有意义,并且你和你的同事觉得这样做很舒服。不要使用不变的结构,因为它们很酷或者很“聪明”。复杂而富有挑战性的解决方案将使您在Uni获得加分,但在商业世界中,我们需要简单的解决方案来解决复杂问题!:) 你的两个主要问题:
def max(xs: List[Int]): Option[Int] = xs match {
case Nil => None
case List(x: Int) => Some(x)
case x :: y :: rest => max( (if (x > y) x else y) :: rest )
}
def divisibleByN(n: Int, xs: List[Int]): List[Int] = xs match {
case Nil => Nil
case x :: rest if x % n == 0 => x :: divisibleByN(n, rest)
case _ :: rest => divisibleByN(n, rest)
}
xs.tail.foldRight(xs.head) {(a, b) => if (a > b) a else b}
xs.reduce {(a, b) => if (a > b) a else b}
xs.foldRight(Nil: List[Int]) {(x, acc) => if (x % 6 == 0) x :: acc else acc}
xs filter { _ % 6 == 0 }
def max(x: List[Int]): Int = {
if (x.isEmpty == true) {
0
}
else {
Math.max(x.head, max(x.tail))
}
}
val a_list = List(a,b,c,d)
max_value = max(a_list)