Dima
Dima

Reputation: 40500

Is it possible to call an overridden method from self type?

Consider this:

 class Foo { def foo = "foo" }
 trait Bar { self: Foo =>
    override def foo = "bar"
 }

I was pleasantly surprised to find out that this is possible, and works as expected:

new Foo with Bar foo 

returns "bar". The question is whether it is possible for Bar.foo to invoke Foo.foo, like one would often do in the "ordinary" inheritance case. override def foo = super.foo + "bar" does not work (says "foo is not a member of AnyRef), and neither does override def foo = self.foo + "bar" (it ends up just calling itself, and results in infinite recursion). I tried a few other combinations (like self.Foo.foo, Foo.this.foo etc.), but without any luck.

Is this just impossible?

Upvotes: 15

Views: 2321

Answers (3)

dkolmakov
dkolmakov

Reputation: 667

No. It is impossible to call overridden method from a self type.

Firstly the trait Bar is not a successor of class Foo so it is not possible using super.foo.

And secondly it is also not possible using self.foo since self is actually of type Bar with Foo. It can be shown by printing the program after typer:

$ scalac -Xprint:typer test.scala
[[syntax trees at end of                     typer]] // test.scala
package <empty> {
  class Foo extends scala.AnyRef {
    def <init>(): Foo = {
      Foo.super.<init>();
      ()
    };
    def foo: String = "foo"
  };
  abstract trait Bar extends scala.AnyRef { self: Bar with Foo => 
    def /*Bar*/$init$(): Unit = {
      ()
    };
    override def foo: String = "bar"
  };
  class FooBar extends Foo with Bar {
    def <init>(): FooBar = {
      FooBar.super.<init>();
      ()
    }
  };
  object TestApp extends scala.AnyRef {
    def <init>(): TestApp.type = {
      TestApp.super.<init>();
      ()
    };
    def main(args: Array[String]): Unit = {
      val a: FooBar = new FooBar();
      scala.this.Predef.println(a.foo)
    }
  }
}

So with self.foo you are trying to access the method foo of the trait Bar. Such behavior matches the Scala Specification (PDF):

The sequence of template statements may be prefixed with a formal parameter definition and an arrow, e.g. x =>, or x: T =>. If a formal parameter is given, it can be used as an alias for the reference this throughout the body of the template. If the formal parameter comes with a type T, this definition affects the self type S of the underlying class or object as follows: Let C be the type of the class or trait or object defining the template. If a type T is given for the formal self parameter, S is the greatest lower bound of T and C. If no type T is given, S is just C. Inside the template, the type of this is assumed to be S.

It is possible to access the method using reflection but I think that it is not what you are looking for.

Upvotes: 5

Mifeet
Mifeet

Reputation: 13618

You make your trait extend Foo instead of using the self type:

class Foo {def foo = "foo"}
trait Bar extends Foo {
  override def foo = super.foo + "bar"
}
new Foo with Bar foo // barfoo

See also this answer.

Upvotes: 0

DanielM
DanielM

Reputation: 1053

I am not aware of any particular syntax to disentangle the base class and the mixed-in trait. There is, however, an easy solution to achieve the result manually by distinguishing the overridden method from the default implementation in the base class:

class Foo { def foo = defaultFoo; def defaultFoo = "foo" }
trait Bar { self: Foo => override def foo = self.defaultFoo + "bar" }

As expected

new Foo with Bar foo == "foobar"
new Foo foo == "foo"

Upvotes: 0

Related Questions