Почему Scala создает новый Tuple при распаковке Tuple? - PullRequest
8 голосов
/ 19 ноября 2011

Почему этот код Scala:

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

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

генерирует следующий байт-код для bar(), который создает новый Tuple2, передает ему Tuple2 из foo() и затем получает значенияиз этого?

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

Это потому, что компилятор не проверяет, что возвращаемое значение foo() не является кортежем?

Будет ли JVM оптимизировать конструкцию в любом случае?

Ответы [ 2 ]

4 голосов
/ 19 ноября 2011

Похоже, что это в соответствии с spec 4.1 Объявления и определения значений - слегка переформатирован для отображения в стеке):

Определения значений могутальтернативно иметь образец (§8.1) как левая сторона.Если p - это какой-то шаблон, отличный от простого имени или имени, за которым следуют двоеточие и тип, определение значения val p = e расширяется следующим образом:

  1. Если шаблон p имеет ограничениепеременные x1, . . . , xn, где n >= 1: Здесь $x - новое имя.
  val $x = e match {case p => (x1, . . . , xn)}
  val x1 = $x._1
  . . .
  val xn = $x._n

Таким образом, создание кортежа происходит на этапе синтаксического анализа.Таким образом, val (i, s) = (1, "s") увеличивается в конце фазы синтаксического анализатора до:

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

Измеряя это в этом простом тесте на миллион итераций:

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 }

приводит к

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

и с флагом -optimize:

foo: Elapsed: 0.027
bar: Elapsed: 0.049
._1 ._2 access: Elapsed: 0.029
4 голосов
/ 19 ноября 2011

Похоже, упущенная возможность оптимизации в скалаке.

Соответствующая часть компилятора us Unapplies # caseClassUnapplyReturnValue , которая вызывает TreeDSL#SOME для генерации кода для создания нового TupleN

...