Getting the desugared part of a Scala for/comprehension expression?

10,364

Solution 1

It doesn't seem to exists any possibilities to desugar "for/comprehension" expressions directly within the REPL. But as an alternative one can use some Scala compiler options like "-print" or for simple expressions "Xprint:typer -e"

Example:

To get the desugard output from a file use the "-print" flag:

# scala -print file.scala

To desugar a simple one-liner expression, use the "-Xprint:typer -e" flag:

# scala -Xprint:typer -e "for (i <- 0 to 100) yield i"

Solution 2

As I already said in the other topic, scalac -print prints out scala code, not java. It translates all scala keywords that are not directly compatible with java to normal scala code. It is not possible to let the compiler translate only parts afaik. But basically a for-comprehension is always translated the same way.

A simple for/yield like this

for(x <- List(1,2,3)) yield x*x

will be translated to

List(1,2,3).map {x => x*x}

And without yield

for(x <- List(1,2,3)) println(x)

to

List(1,2,3).foreach{x => println(x)}

Nested fors will be translated to nested flatMap/map constructs

for(x <- List(1,2,3); y <- List(4,5,6)) yield x*y

will be translated to

List(1,2,3).flatMap { x =>
  List(4,5,6).map { y =>
    x*y
  }
}

So there is absolutely no magic

Solution 3

How about a macro?

import scala.reflect.macros.Context
import scala.reflect.runtime.universe._
import scala.language.experimental.macros

def _desugar(c : Context)(expr : c.Expr[Any]): c.Expr[Unit] = {
  import c.universe._
  println(show(expr.tree))
  reify {}
}

def desugar(expr : Any) = macro _desugar

This can be used directly in the REPL, as per your request:

scala> desugar { for(i <- List(1,2,3,4,5)) yield i }
immutable.this.List.apply[Int](1, 2, 3, 4, 5).map[Int, Any](((i: Int) =>
i))(immutable.this.List.canBuildFrom[Int])

scala> desguar { for(i <- (0 to 10) if (i > 5)) yield i }
scala.this.Predef.intWrapper(0).to(10).withFilter(((i: Int) => i.>(5))).map[Int,
Any](((i: Int) => i))(immutable.this.IndexedSeq.canBuildFrom[Int])

It also works on other arbitrary expressions.

scala> desugar {
     |   val x = 20
     |   val y = 10
     |   println(x + y)
     | }
{
  val x: Int = 20;
  val y: Int = 10;
  scala.this.Predef.println(x.+(y))
}

This is probably the closest you'll get to what you're asking without ever having to compile or dump the data out to a file at any point. You can define the macro directly in the REPL, or in an external file loaded with the :load command.

Solution 4

To see the result after simple desugaring use the -Xprint:parser option.

If you have this simple input file named test.scala:

object Test {
  for(x <- List(1,2,3); y <- List(4,5,6)) yield x*y
}

Then compiling it using scalac -Xprint:parser prints out:

$ scalac -Xprint:parser test.scala 
[[syntax trees at end of                    parser]] // test.scala
package <empty> {
  object Test extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    List(1, 2, 3).flatMap(((x) => List(4, 5, 6).map(((y) => x.$times(y)))))
  }
}

To get a complete list of compiler phases applicable to -Xprint:<phase> do this:

$ scalac -Xshow-phases
             phase name  id  description
             ----------  --  -----------
                 parser   1  parse source into ASTs, perform simple desugaring
                  namer   2  resolve names, attach symbols to named trees
         packageobjects   3  load package objects
                  typer   4  the meat and potatoes: type the trees
                 patmat   5  translate match expressions
         superaccessors   6  add super accessors in traits and nested classes
             extmethods   7  add extension methods for inline classes
                pickler   8  serialize symbol tables
              refchecks   9  reference/override checking, translate nested objects
           selectiveanf  10  
           selectivecps  11  
                uncurry  12  uncurry, translate function values to anonymous classes
              tailcalls  13  replace tail calls by jumps
             specialize  14  @specialized-driven class and method specialization
          explicitouter  15  this refs to outer pointers, translate patterns
                erasure  16  erase types, add interfaces for traits
            posterasure  17  clean up erased inline classes
               lazyvals  18  allocate bitmaps, translate lazy vals into lazified defs
             lambdalift  19  move nested functions to top level
           constructors  20  move field definitions into constructors
                flatten  21  eliminate inner classes
                  mixin  22  mixin composition
                cleanup  23  platform-specific cleanups, generate reflective calls
                  icode  24  generate portable intermediate code
                inliner  25  optimization: do inlining
inlineExceptionHandlers  26  optimization: inline exception handlers
               closelim  27  optimization: eliminate uncalled closures
                    dce  28  optimization: eliminate dead code
                    jvm  29  generate JVM bytecode
               terminal  30  The last phase in the compiler chain

The -Xprint:<phase> option is also applicable to scala and thus to the REPL. However, you will see all the wrapper code the REPL inserts as well.

$ scala -Xprint:parser
Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

<..a lot of initialisation code printed..>

scala> object Test {
     |   for(x <- List(1,2,3); y <- List(4,5,6)) yield x*y
     | }
[[syntax trees at end of                    parser]] // <console>
package $line3 {
  object $read extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    object $iw extends scala.AnyRef {
      def <init>() = {
        super.<init>();
        ()
      };
      object $iw extends scala.AnyRef {
        def <init>() = {
          super.<init>();
          ()
        };
        object Test extends scala.AnyRef {
          def <init>() = {
            super.<init>();
            ()
          };
          List(1, 2, 3).flatMap(((x) => List(4, 5, 6).map(((y) => x.$times(y)))))
        }
      }
    }
  }
}

[[syntax trees at end of                    parser]] // <console>
package $line3 {
  object $eval extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    lazy val $result = $line3.$read.$iw.$iw.Test;
    val $print: String = {
      $read.$iw.$iw;
      "".$plus("defined module ").$plus("Test").$plus("\n")
    }
  }
}

defined module Test

scala> 

Solution 5

Intellij has a feature called "Explain Scala" that does a LOT of desugaring including expanding for comprehensions into map/flatMap/filter directly in the file you are editing.

Please note that since IntelliJ 2017.1 this is now called "Desugar Scala Code" and is in the "Code" menu (thanks Mikaël for the info).

IntelliJ Desugar Scala

Share:
10,364
IODEV
Author by

IODEV

Updated on June 06, 2022

Comments

  • IODEV
    IODEV almost 2 years

    Does anyone know how to get the (Scala part only) desugared translation of a for/comprehension expression before it actually tries to compile in the REPL (or compiler)?

    The only thing I've found so far is the compiler "-print" flag but that gives you the full Scala translation…