Bob Yacobellis
Bob Yacobellis

Reputation: 75

Scala for comprehension vs. foreach

In the Scala shell I did this:

import java.util._
import scala.collection.JavaConversions._

val t: SortedMap[String,Int] = new TreeMap[String,Int] () // produces an empty java.util.SortedMap

t("a") = 1; t("b") = 2; t("A") = 3; t("0") = 4

t // prints: res35: java.util.SortedMap[String,Int] = {0=4, A=3, a=1, b=2}, as expected

t foreach println // also prints the (k,v) pairs in the same TreeMap sorted order

However, the following statement does not print the pairs in sorted order, it appears to print them in hash bucket order, (0,4) (b,2) (A,3) (a,1):

for ((k,v) <- t) printf("(%s,%d)%n", k, v)

In other answers related to for and foreach it seems that a for comprehension is supposed to translate into a use of foreach like so:

"A for-comprehension for (p <- e) e0 is translated to e.foreach { case p => e0 }"

but that does not seem to be what is happening here.

Note that if I create a scala SortedMap from a scala TreeMap that both the foreach and the for produce the (k,v) pairs in sorted order as I expected. It seems like some way that the Java TreeMap is being converted for scala is different.

Any comments or ideas about why this discrepancy?

Upvotes: 4

Views: 1980

Answers (1)

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297305

Well, p and (k,v) are not the same thing. Your for-comprehension is translated thus:

t.filter{
  case (k, v) => true
  case _      => false
}.map {
  case (k, v) => printf("(%s,%d)$n")
}

Once filter puts it's hands on the java collection, it becomes a Scala collection, and is no longer sorted.

As a side note, on Scala 2.10 the above is no longer true since it detects that there's no element that isn't a (k,v) from the static type at compile time, and doesn't generate the filter. Here:

scala> for ((k,v) <- t) printf("(%s,%d)%n", k, v)
<console>:15: warning: dead code following this construct
              for ((k,v) <- t) printf("(%s,%d)%n", k, v)
                         ^
(0,4)
(A,3)
(a,1)
(b,2)

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> reify{for ((k,v) <- t) printf("(%s,%d)%n", k, v)}
res4: reflect.runtime.universe.Expr[Unit] =
Expr[Unit](scala.collection.JavaConversions.mapAsScalaMap(t).foreach(((x$1) => x$1: @unchecked match {
  case scala.Tuple2((k @ _), (v @ _)) => scala.this.Predef.printf("(%s,%d)%n", k, v)
})))

PS: Prefer JavaConverters over JavaConversions.

Upvotes: 7

Related Questions