WeiChing 林煒清
WeiChing 林煒清

Reputation: 4469

the for-comprehension with assignment loops in wrong order

given val l = List( List(0), List(1) )

The for-loop :

for {
  x <- l
  _ = println(x)
  y <- x
} {println(y)}

//would prints :

List(0)  
List(1)  
0  
1 

The prints are in the wrong order !!

Isn't it be translated into the following? the translation of for-loop :

l.foreach(
  x => {
    println(x)
    x.foreach(y => println(y))
  }
)


List(0)  
0  
List(1)  
1  

---

My Questions:

  1. Why the for-loop is not executed by intuitive order ? (I was expecting the result is of the second example using foreach )
  2. Is my translation wrong ?
  3. Why we must assign something (e.g. _ = print()) in for-condition part? (just a print() will not compile )

Upvotes: 2

Views: 2263

Answers (2)

Marth
Marth

Reputation: 24802

The problem here is your translation of the for loop (which mostly comes from the erronous translation of the _ = println(x)).

From the Scala Specs, you can see (page 90) :

• A generator p <- e followed by a value definition p' = e' is translated to the following generator of pairs of values, where x and x' are fresh names:

(p, p' ) <- for (x@p <- e) yield { val x'@p' = e' ; (x, x') }

Applying the transformations step by step, you get :

for {
  (x, u) <- for (x <- l) yield {val u = println(x); (x, u)}
  y <- x
} {println(y)}

for {
  (x,u) <- l.map {case x => val u = println(x); (x,u)}
  y <- x
} {println(y)}

l.map     { case x => val u = println(x); (x,u) }
 .foreach { case (x,u) => for ( y <- x ) {println(y)} }

l.map     { case x => val u = println(x); (x,u) }
 .foreach { case (x,u) => x.foreach {case y => println(y)} }

and

scala> :paste
// Entering paste mode (ctrl-D to finish)

    l.map     { case x => val u = println(x); (x,u) }
     .foreach { case (x,u) => x.foreach {case y => println(y)} }    

// Exiting paste mode, now interpreting.

List(0)
List(1)
0
1

Upvotes: 2

phipsgabler
phipsgabler

Reputation: 20950

The loop you gave gets translated to the following:

l.map(((x) => {
  val x$1 = println(x);
  scala.Tuple2(x, x$1)
})).foreach(((x$2) => x$2: @scala.unchecked match {
  case scala.Tuple2((x @ _), (x$1 @ _)) => x.foreach(((y) => println(y)))
}))

You can find out things like this by quasiquoting them in an interpreter.

What happens is that the assignment is associated with the generator before it -- like if you wrote for (x <- l; h = x.head), where coupling these two is obiously necessary.

If you want the side effect to happen for each subsequent generator, you have to write the follwing:

for {
  x <- l
  y <- {println(x); x}
} {println(y)}

This results in your desired printout, and compiles quite to what you had expected:

l.foreach(((x) => {
  println(x);
  x
}.foreach(((y) => println(y)))))

As to why explicitly discarding arguments in generators this is necessary -- there are two issues. First, println is in a different monad. Running expressions only for effect in monad comprehensions only make sense in the same monad, obviously; and that's also not really useful in the list monad. If you were working in a hypothetical IO monad, you would be able to do the following:

for {
  y <- readLn()
  _ <- printLn(x)
}

But here comes the second issue: Scala does not even here allow to discard unit results, we still have to pattern match on _.

Don't ask me why, it's just in the standard. IMHO, it would actually make sense to allow this, as Haskell does. One reason I could think of, however, is just what you are trying to do: mixing side effects of one monad with another. They could have wanted to ban this once and for all. It could also have to do with the fact that rewriting of for comprehensions in Scala is based more on duck typing than it is in Haskell, has maybe more corner cases and even happens (as far as I know) before type checking, which would complicate such "mixing" a lot.

Upvotes: 3

Related Questions