为什么我应该避免在Scala中使用局部可修改变量?

为什么我应该避免在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

我对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

  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)