scalafor-comprehensionscalac

Problem in scala for-comprehension de-sugar?


I have the following for-comprehension which I de-sugared using command scala -Xprint:parser ForComprehensionDemo.scala. When I copy the de-sugared method and call it, it produces different result than the for-comprehension. Any idea why?

For comprehension:

    object ForComprehensionDemo {
      def main(args: Array[String]): Unit = {
        forWithIf(true)
      }

      def forWithIf(condition: Boolean) = {
        val x = for {
          a <- name(0)
          b <- if (condition) {
            name(1)
            name(2)
          } else {
            name(100)
          }
          c <- name(2)
        } yield {
          a + b + c
        }

        println(x)
      }

   def name(x: Int): Option[String] = {
     println("called for :" + x)
     x match {
       case 0 => Some(" aa ")
       case 1 => Some(" bb ")
       case 2 => Some(" cc ")
       case _ => Some(" not identified ")
     }
    }   
   }

Produces result:

called for :0
called for :1
called for :2
called for :2
Some( aa  cc  cc )

De-sugared code

    def forWithIf(condition: Boolean) = {
      val x = name(0).flatMap(((a) => if (condition)
  {
    name(1);
    name(2)
  }
else
  name(100).flatMap(((b) => name(2).map(((c) => a.$plus(b).$plus(c)))))));
      println(x)
    };

Produces result:

called for :0
called for :1
called for :2
Some( cc )

Solution

  • Just a bug in the pretty printer. It's missing parens around the if-else.

    In general, scala 2 doesn't represent parens very faithfully, but scala 3 is much better.

    package desugar {
      object ForComprehensionDemo extends scala.AnyRef {
        def main(args: Array[String]): Unit = forWithIf(true);
        def forWithIf(condition: Boolean) = {
          val x = name(0).flatMap(((a) => (if (condition)
      {
        name(1);
        name(2)
      }
    else
      name(100)).flatMap(((b) => name(2).map(((c) => a.$plus(b).$plus(c)))))));
          println(x)
        };
        def name(x: Int): Option[String] = {
          println("called for :".$plus(x));
          x match {
            case 0 => Some(" aa ")
            case 1 => Some(" bb ")
            case 2 => Some(" cc ")
            case _ => Some(" not identified ")
          }
        }
      }
    }
    

    I was curious to see what Scala 3 says. -Xprint:all says after typer:

              sugar.ForComprehensionDemo.name(0).flatMap[String](
                {
                  def $anonfun(a: String): Option[String] = 
                    (if condition then 
                      {
                        sugar.ForComprehensionDemo.name(1)
                        sugar.ForComprehensionDemo.name(2)
                      }
                     else 
                      {
                        sugar.ForComprehensionDemo.name(100)
                      }
                    ).flatMap[String](
                      {
                        def $anonfun(b: String): Option[String] = 
                          sugar.ForComprehensionDemo.name(2).map[String](
                            {
                              def $anonfun(c: String): String = 
                                {
                                  a.+(b).+(c)
                                }
                              closure($anonfun)
                            }
                          )
                        closure($anonfun)
                      }
                    )
                  closure($anonfun)
                }
              )
    

    That's more inscrutible with the closures, but it has parens. Printing after parser isn't useful.