0
object Test {
  trait Foo
  trait Bar {
    def someMethod(): Unit
    val someValue: String
  }

  trait Runner {
    protected type A <: Foo

    def run(a: A): Unit
  }

  trait Runner2 {
    this: Runner =>

    protected type A <: Foo with Bar

    def run(a: A): Unit = a.someMethod()
  }
}

In the above example, if Runner2 is not self-typed as Runner, the code compiles, but if it is, it does not:

Error:(42, 29) value someMethod is not a member of Runner2.this.A
    def run(a: A): Unit = a.someMethod()

I understand this is because the self-typing causes Runner.A to "take precedence" over Runner2's override, but - why?

If I don't use the self-typing, other issues arise with concrete implementations of Runner2, since such implementations extend Runner and mix-in Runner2.

What's the best way to handle such relationships?

jwvh
  • 50,871
  • 7
  • 38
  • 64
Scala Newb
  • 271
  • 5
  • 12

1 Answers1

0

I think in this case, the answer is right in front of you... ;-)

The error states: value someMethod is not a member of Runner2.this.A.

You have declared Runner2.this to be of type Runner, and A - as defined in trait Runner - does not have a method called someMethod. Hence the compiler error.

When you remove the self-typing, it compiles because now Runner2's type A (of type Foo with Bar) does have someMethod.

Self-typing is essentially a statement that the associated trait will be mixed in with the specified type (or a subclass of that type) before being used. If you try to use an instance of Runner2 (with the self-type declaration) without an instance of Runner being mixed-in with it, you'll get a compiler error.

For example:

abstract class X extends Runner2

will not compile because Runner was not mixed in. (error: illegal inheritance; self-type X does not conform to Runner2's selftype Runner2 with Runner.) However, this would work (in principle, however there are other problems - see below):

abstract class X extends Runner2 with Runner

What self-typing does is to give you access to the indicated type's elements. This is essential for one of the approaches to handling dependency injection in Scala. (For further information, refer to Daniel Sobral's answer to this question.)

However, it is Runner that extends Runner2 not the other way around, so you would also discover that the compiler will complain that Runner's definition of A has an incompatible type (since Foo is not a subclass of Foo with Bar).

Community
  • 1
  • 1
Mike Allen
  • 8,139
  • 2
  • 24
  • 46