为什么我的不可变对象会改变Scala

时间:2013-03-16 20:59:33

标签: debugging scala immutability

我在下面的代码中遇到了一个最令人不安的错误。即使我将它作为不可变的Map传递,按钮Map也会发生变异。键保持不变,并且地图指向两个不可变的Ints,但在下面您可以看到地图在运行期间明显具有不同的值。我绝对难过,不知道发生了什么。

def makeTrace(trace : List[(String)], buttons : Map[String, (Int,Int)],
  outputScreen : ScreenRegion, hashMap : Map[Array[Byte], String])
  : (List[(String,String)], Map[Array[Byte], String]) = {

println(buttons.toString)
//clearing the device
val clear = buttons.getOrElse("clear", throw new Exception("Clear Not Found"))
//clear.circle(3000)
val thisButton = new ScreenLocation(clear._1, clear._2)
click(thisButton)

//updates the map and returns a list of (transition, state)
trace.foldLeft((Nil : List[(String,String)], hashMap))( (list, trace) => {
  println(buttons.toString)
  val transition : String = trace
  val location = buttons.getOrElse(transition, throw new Exception("whatever"))
  val button = new ScreenLocation(location._1, location._2)
  button.circle(500)
  button.label(transition, 500)
  click(button)

  //reading and hashing
  pause(500)
  val capturedImage : BufferedImage = outputScreen.capture()
  val outputStream : ByteArrayOutputStream = new ByteArrayOutputStream();
  ImageIO.write(capturedImage, "png", outputStream)
  val byte : Array[Byte] = outputStream.toByteArray();
  //end hash

  //if our state exists for the hash
  if (hashMap.contains(byte)){ list match {
    case (accumulator, map) => ((transition , hashMap.getOrElse(byte, throw new Exception("Our map broke if"))):: accumulator, map)
  }
  //if we need to update the map
  }else list match {
    case (accumulator, map) => {
      //adding a new state based on the maps size
      val newMap : Map[Array[Byte], String] = map + ((byte , "State" + map.size.toString))
        val imageFile : File = new File("State" + map.size.toString + ".png");
        ImageIO.write(capturedImage, "png", imageFile);
      ((transition, newMap.getOrElse(byte, throw new Exception("Our map broke else"))) :: accumulator, newMap)
    }        
  }  
})

}

在我调用此函数之前,我将映射初始化为指向不可变对象的不可变映射。

    val buttons = makeImmutable(MutButtons)
    val traceAndMap = TraceFinder.makeTrace(("clear" ::"five"::"five"::"minus"::"five"::"equals":: Nil), buttons, outputScreen, Map.empty)

makeImmutable在哪里

def makeImmutable(buttons : Map[String, (Int,Int)]) : Map[String, (Int,Int)] = {
  buttons.mapValues(button => button match {
    case (x, y) => 
      val newX = x
      val newY = y
      (newX,newY)
  })
}

这是输出,你可以看到clear,minus和five

的状态变化
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (959,345), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (881,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,441), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (881,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))

2 个答案:

答案 0 :(得分:2)

首先,尝试在代码周围添加println(map.getClass)。确保它确实是您认为的地图。它的包名应该有immutable,例如:

scala> println(Map(1->1, 2->2, 3->3, 4->4).getClass)
class scala.collection.immutable.Map$Map4

其次,确保你真的打印出你认为的完全相同的地图;使用引用标识哈希码是一种很好的方法:println(System.identityHashCode(map))

这些事情中的一个不会给你预期的结果,这是非常好的。然后你只需要弄清楚问题泄漏的地方。如果没有一个完整的可运行的例子,如果没有过多的代码盯着,很难提供更好的建议。

答案 1 :(得分:0)

我怀疑你在makeImmutable函数的范围内有scala.collection.Map的导入,它允许你将MutButtons(可能是一个可变的Map)传递给makeImmutable函数。 Map上的mapValues方法返回基础地图的视图,而不是不可变副本。例如:

import scala.collection.Map
import scala.collection.mutable.{Map => MutableMap}

def doubleValues(m: Map[Int, Int]) = m mapValues { _ * 2 }

val m = MutableMap(1 -> 1, 2 -> 2)
val n = doubleValues(m)
println("m = " + m)
println("n = " + n)
m += (3 -> 3)
println("n = " + n)

运行此程序会产生以下输出:

m = Map(2 -> 2, 1 -> 1)
n = Map(2 -> 4, 1 -> 2)
n = Map(2 -> 4, 1 -> 2, 3 -> 6)

要从makeImmutable返回一个真正不可变的映射,请在映射值后调用.toMap。