Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Scala construct a new Tuple when unpacking a Tuple?

Why does this Scala code:

class Test
{
  def foo: (Int, String) =
  {
    (123, "123")
  }

  def bar: Unit =
  {
    val (i, s) = foo
  }
}

generate the following bytecode for bar() that constructs a new Tuple2, passes the Tuple2 from foo() to it and then gets the values out of it?

public void bar();
Code:
 0:   aload_0
 1:   invokevirtual   #28; //Method foo:()Lscala/Tuple2;
 4:   astore_2
 5:   aload_2
 6:   ifnull  40
 9:   new     #7; //class scala/Tuple2
 12:  dup
 13:  aload_2
 14:  invokevirtual   #32; //Method scala/Tuple2._1:()Ljava/lang/Object;
 17:  aload_2
 18:  invokevirtual   #35; //Method scala/Tuple2._2:()Ljava/lang/Object;
 21:  invokespecial   #20; //Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
 24:  astore_1
 25:  aload_1
 26:  invokevirtual   #39; //Method scala/Tuple2._1$mcI$sp:()I
 29:  istore_3
 30:  aload_1
 31:  invokevirtual   #35; //Method scala/Tuple2._2:()Ljava/lang/Object;
 34:  checkcast       #41; //class java/lang/String
 37:  astore  4

Is this because the compiler isn't checking that foo()s return value isn't a tuple?

Will the JVM optimize the construction away anyway?

like image 909
Blair Zajac Avatar asked Nov 19 '11 07:11

Blair Zajac


2 Answers

This seems to be according to the spec (in 4.1 Value Declarations and Definitions - slightly reformatted for stackoverflow display):

Value definitions can alternatively have a pattern (§8.1) as left-hand side. If p is some pattern other than a simple name or a name followed by a colon and a type, then the value definition val p = e is expanded as follows:

  1. If the pattern p has bound variables x1, . . . , xn, where n >= 1: Here, $x is a fresh name.
  val $x = e match {case p => (x1, . . . , xn)}
  val x1 = $x._1
  . . .
  val xn = $x._n

So the tuple creation happens in the parser phase. So val (i, s) = (1, "s") expends at the end of the parser phase to:

private[this] val x$1 = scala.Tuple2(1, "s"): @scala.unchecked match {    
  case scala.Tuple2((i @ _), (s @ _)) => scala.Tuple2(i, s)
};
val i = x$1._1;
val s = x$1._2

Measuring this on this simple test on a million iterations:

def foo: (Int, String) = (123, "123")
def bar: Unit = { val (i, s) = foo }
def bam: Unit = { val f = foo; val i = f._1; val s = f._2 }

yields

foo: Elapsed: 0.030
bar: Elapsed: 0.051
._1 ._2 access: Elapsed: 0.040

and with -optimize flag:

foo: Elapsed: 0.027
bar: Elapsed: 0.049
._1 ._2 access: Elapsed: 0.029
like image 135
huynhjl Avatar answered Nov 07 '22 19:11

huynhjl


It seems like a missed optimization opportunity in scalac.

The relevant part of the compiler us Unapplies#caseClassUnapplyReturnValue, which calls TreeDSL#SOME to generate code to create a new TupleN

like image 4
retronym Avatar answered Nov 07 '22 19:11

retronym