Anonymous subclasses with extra methods -- scala2 vs. scala3

64 views Asked by At

The following code works as (I) expect in scala2 but generates a compile-time error in scala3. I tried to find an explanation in Programming in Scala, Fifth Edition and the web but failed. Any insights?

object Main {
    def main(args:Array[String]): Unit = {

        val foo = new Foo(7)
        println(foo.double)             // 14

        val bar = new Foo(8) {
            def triple:Int = this.i * 3
        }

        println(bar.double)             // 16
        println(bar.triple)             // 24 in scala2; compile error in scala3
    }
}

class Foo(val i:Int) {
    def double:Int = i*2
}

Compilation error:

-- [E008] Not Found Error: /tmp/example.scala:12:20 ----
12 |        println(bar.triple)
   |                ^^^^^^^^^^
   |                value triple is not a member of Foo
2

There are 2 answers

3
Andrey Tyukin On BEST ANSWER

If you compile your code with scala -Xprint:typer , you'll see that val bar now explicitly has type Foo in Scala 3:

    def main(args: Array[String]): Unit = 
      {
        val foo: Foo = new Foo(7)
        println(foo.double)
        val bar: Foo = 
          {
            final class $anon() extends Foo(8) {
              def triple: Int = this.i.*(3)
            }
            new $anon():Foo
          }
        println(bar.double)
        println(bar.triple)
      }

Contrast this to Scala 2, where bar gets the refinement type Foo { def triple: Int }:

      val bar: Foo{def triple: Int} = {
        final class $anon extends Foo {
          def <init>(): <$anon: Foo> = {
            $anon.super.<init>(8);
            ()
          };
          def triple: Int = this.i.*(3)
        };
        new $anon()
      };

So, it's

val bar: Foo                   // In Scala 3
val bar: Foo{def triple: Int}  // In Scala 2

To achieve the behavior of Scala 2, you could introduce a new class Bar explicitly:

        class Bar extends Foo(8) {
          def triple: Int = this.i * 3
        }
        val bar = new Bar

Full compilable code:

object Main {
    def main(args:Array[String]): Unit = {

        val foo = new Foo(7)
        println(foo.double)             // 14

        class Bar extends Foo(8) {
          def triple: Int = this.i * 3
        }
        val bar = new Bar

        println(bar.double)             // 16
        println(bar.triple)             // 24 in scala2; compile error in scala3
    }
}

class Foo(val i:Int) {
    def double:Int = i*2
}

As for the reason why it's inferring Foo instead of Foo { def triple: Int } - I'm not exactly sure, I guess they just figured out eventually that this behavior plays better with other language features.

How would you explain that val x = "hello" infers x: String and not x: "hello"?

0
Seth Tisue On

This is covered in the Scala 3 migration guide at https://docs.scala-lang.org/scala3/guides/migration/incompat-type-inference.html#reflective-type:

Scala 2 reflective calls are dropped and replaced by the broader Programmatic Structural Types.