vsnyc vsnyc - 1 month ago 16
Java Question

How does Scala distinguish between () => T and => T

My other question got closed as a duplicate, so I'll try this again. I have also read this question and what I'm asking is different. I'm interested in learning the internal implementation of how

Call-by-Name: => Type
differs from
() => Type
.

My confusion is coming from looking at javap and cfr disassembly which shows no difference in the two cases.

e.g. ParamTest.scala:

object ParamTest {
def bar(x: Int, y: => Int) : Int = if (x > 0) y else 10
def baz(x: Int, f: () => Int) : Int = if (x > 0) f() else 20
}


javap output
javap ParamTest.scala
:

public final class ParamTest {
public static int baz(int, scala.Function0<java.lang.Object>);
public static int bar(int, scala.Function0<java.lang.Object>);
}


CFR Decompiled output
java -jar cfr_0_118.jar ParamTest$.class
:

import scala.Function0;

public final class ParamTest$ {
public static final ParamTest$ MODULE$;

public static {
new ParamTest$();
}

public int bar(int x, Function0<Object> y) {
return x > 0 ? y.apply$mcI$sp() : 10;
}

public int baz(int x, Function0<Object> f) {
return x > 0 ? f.apply$mcI$sp() : 20;
}

private ParamTest$() {
MODULE$ = this;
}
}


EDIT 1:
Scala Syntax Tree:
scalac -Xprint:parse ParamTest.scala


package <empty> {
object ParamTest extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def bar(x: Int, y: _root_.scala.<byname>[Int]): Int = if (x.$greater(0))
y
else
10;
def baz(x: Int, f: _root_.scala.Function0[Int]): Int = if (x.$greater(0))
f()
else
20
}
}


EDIT 2: Mailing list research:

Read this interesting post on the mailing list which essentially states that
=> T
is implemented as
() => T
. Quote:


First, look at

f: => Boolean


Although this is called a "by-name parameter", it is actually implemented as a
Function0
,

f: () => Boolean


just with different syntax used on both ends.


Now I'm even more confused by this answer which explicitly states that the two are different.

Questions:


  • How is Scala distinguishing
    bar
    from
    baz
    ? The method signatures (not implementation) for both are identical in the decompiled code.

  • Does the difference in the two scenarios not get persisted into the compiled bytecode?

  • Is the decompiled code inaccurate?

  • Added after Edit 1: I found that the scalac syntax tree does show a difference,
    bar
    has the second argument of type
    _root_.scala.<byname>[Int]
    . What does it do? Any explanation, pointers in scala source or equivalent pseudo code will be helpful.

  • See EDIT 2 above: Is the quoted block correct? As in, is
    => T
    a special subclass of
    Function0
    ?


Answer

How is Scala distinguishing bar from baz? The method signatures (not implementation) for both are identical in the decompiled code.

Scala doesn't need to distinguish between the two. From it's perspective, these are two different methods. What's interesting (to me at least) is that if we rename baz into bar and try to create an overload with a "call-by-name" parameter, we get:

Error:(12, 7) double definition:
method bar:(x: Int, f: () => Int)Int and
method bar:(x: Int, y: => Int)Int at line 10
have same type after erasure: (x: Int, f: Function0)Int
  def bar(x: Int, f: () => Int): Int = if (x > 0) f() else 20

Which is a hint for us that under the covers, something is going on with the translation to Function0.

Does the difference in the two scenarios not get persisted into the compiled bytecode?

Before Scala emits JVM bytecode, it has additional phases of compilation. An interesting one in this case is to look at the "uncurry" stage (-Xprint:uncurry):

[[syntax trees at end of uncurry]] 
package testing {
  object ParamTest extends Object {
    def <init>(): testing.ParamTest.type = {
      ParamTest.super.<init>();
      ()
    };
    def bar(x: Int, y: () => Int): Int = if (x.>(0))
      y.apply()
    else
      10;
    def baz(x: Int, f: () => Int): Int = if (x.>(0))
      f.apply()
    else
      20
  }
}

Even before we emit byte code, bar is translated into a Function0.

Is the decompiled code inaccurate

No, it's definitely accurate.

I found that the scalac syntax tree does show a difference, bar has the second argument of type root.scala.[Int]. What does it do?

Scala compilation is done in phases, where each phases output is the input to the next. In addition to the parsed AST, Scala phases also create symbols, such that if one stage relys on a particular implementation detail it will have it available. <byname> is a compiler symbol which shows that this method uses "call-by-name", so that one of the phases can see that and do something about it.