plomovtsev
plomovtsev

Reputation: 542

Why for-comprehension expands to map+foreach instead of nested foreach?

I suppose that for-comprehension which I see as "for every 'a' create 'x' and then for every 'b' do some stuff with all of vars"

for {
    a <- Seq(1, 2, 3)
    x = "test" + a
    b <- Seq(4, 5, 6)
} {
    ...
}

should be expanded to

Seq(1, 2, 3).foreach { a =>
    val x = "test" + a
    Seq(4, 5, 6).foreach { b =>
        ...
    }
}

but surprisingly check with -Xprint:parser shows that it expands to

Seq(1, 2, 3).map { a =>
    val x = "test" + a
    (a, x)
}.foreach { case (a, x) =>
    Seq(4, 5, 6).foreach { b =>
        ...
    }
}

I think it breaks the natural understanding of what's going on in whole for-comprehension, coz now it is firstly define three different 'x' and then executes other stuff. It may be critical if definition of 'x' can produce side-effects so what's the purpose of desugaring to map?

Upvotes: 0

Views: 176

Answers (1)

Kolmar
Kolmar

Reputation: 14224

This behaviour is surprising, but it's documented in Scala specification: https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#for-comprehensions-and-for-loops

According to the last rule there:

A generator 𝑝 <- 𝑒 followed by a value definition 𝑝′ = 𝑒′ is translated to the following generator of pairs of values, where 𝑥 and 𝑥′ are fresh names:

(𝑝, 𝑝′) <- for (𝑥@𝑝 <- 𝑒) yield { val 𝑥′@𝑝′ = 𝑒′; (𝑥, 𝑥′) }

So when there is a value definition, Scala always inserts a new for-comprehension with a yield, which then becomes map.

And if you replace the value definition line x = "test" + a with a generator x <- Seq("test" + a), the result becomes as expected:

Seq(1, 2, 3)
  .foreach(((a) => Seq("test".$plus(a))
    .foreach(((x) => Seq(4, 5, 6)
      .foreach(((b) => ...))))))

Upvotes: 5

Related Questions