2

I have come across a most troubling bug in my code below. The buttons Map mutates even though I pass it as an immutable Map. The keys remain the same, and the map points to two immutable Ints, yet below you can see the map clearly has different values during the run. I am absolutely stumped and have no idea what is happening.

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)
    }        
  }  
})

}

Right before I call this function I initialize the map to an immutable map that points to immutable objects.

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

Where makeImmutable is

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)
  })
}

Here is the output, you can see the state change for clear, minus, and 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))
dakillakan
  • 240
  • 2
  • 9
  • Either the map must be mutating or the *objects* in the map must be mutating. There is no sensible alternative. Find out which - slowly remove all non-necessary code such that the problem still exists. (Well, I suppose it could be a different map being printed somewhere..) –  Mar 16 '13 at 21:02
  • I could not figure out the formatting, but I will insert it above. What I do immediately before calling my function is create a immutable map with immutable objects. I will post the code above. – dakillakan Mar 16 '13 at 21:05

2 Answers2

2

First, try sprinkling println(map.getClass) around your code. Make sure it really is the map you think it is. It should have immutable in its package name, e.g.:

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

Second, make sure you really are printing out the exact same map you think you are; using the reference identity hash code is a good way to do that: println(System.identityHashCode(map)).

Chances are extraordinarily good that one of these things will not give you the expected result. Then you just have to figure out where the problem leaked in. Without a full runnable example it's hard to offer better advice without inordinate amounts of code-staring.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • This is awesome advice, thank you! I figured out my problem, and something about scala as well. It seems that when you use the mapValues function, scala tows around the functions used for the mapping rather than the values! I converted my map into a list and then back into a map again, and that seemed to fix it! EDIT: I am not cool enough to upvote you, sorry – dakillakan Mar 16 '13 at 22:26
  • Glad you fixed it. Another way to map the values strictly is like this: `m map { case (k, v) => (k, f(v)) }`. This constructs a new map immediately. – mpilquist Mar 16 '13 at 22:41
  • @dakillakan This is also the question I asked a few days ago. People provided other possibilities of generating a new `Map` from `mapValues`, which is using `force`. http://stackoverflow.com/questions/14882642/scala-why-mapvalues-produces-a-view-and-is-there-any-stable-alternatives – Kane Mar 16 '13 at 22:43
0

I suspect you have an import for scala.collection.Map in scope of the makeImmutable function, which allows you to pass MutButtons (presumably a mutable Map) to the makeImmutable function. The mapValues method on Map returns a view of the underlying map, not an immutable copy. For example:

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)

Running this program produces this output:

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

To return a truly immutable map from makeImmutable, call .toMap after mapping over the values.

mpilquist
  • 3,855
  • 21
  • 22
  • Thank you for all your help, but I am afraid this is not it. I never declare a mutable map, and my makeImmutable function is there to prove to myself that I am not crazy. I added the .toMap just to be sure and I still have issues with mutation. – dakillakan Mar 16 '13 at 21:42
  • Ah, bummer. I was once bitten by mapValues in that way so I figured that might be the cause. – mpilquist Mar 16 '13 at 21:51