Search code examples
scalatraitsself-type

Named Scala self-type still shadows "this"?


Working through these posts had me thinking I understood self-types, at least somewhat.

So I created an example which failed as expected:

scala> trait A { val v = "a" }
defined trait A

scala> trait B { this :A => ; var v = "" ; this.v = "b" }
<console>:6: error: reassignment to val
       trait B { this :A => ; var v = "" ; this.v = "b" }
                                                  ^

The self-type's "this" shadows B's "this" -- it looks weird, but makes sense.

It would seem wise, then, to give the self-type a different name. I did this and was rather surprised:

scala> trait C { a :A => ; var v = "" ; this.v = "c" }
<console>:6: error: reassignment to val
       trait C { a :A => ; var v = "" ; this.v = "c" }
                                               ^

It's still shadowed???

Changing the name of the 'local' variable let things compile, which makes sense:

scala> trait D { a :A => ; var w = "" ; this.w = a.v }
defined trait D

(And the self-type name can optionally be used to clarify which "v" to use.)

Okay. Which means that the following should fail?

scala> trait E { this :A => ; var w = "" ; this.w = this.v }
defined trait E

Huh? Which this is this? :-(

So... is there a point to naming self-types? "this" seems to end up shadowed regardless.

Or is this an edge-case of scoping rules, where the self-type's "this" takes precedence over the trait's "this" -- and one should just avoid using the same names for things in related traits?


Solution

  • Your problem is not the name of the self type (in all your examples both this and the alternate self-type name refer to the very same thing and have the same type, namely ‘the greatest lower bound of B and A’ [§5.1, Scala Language Spec]) but that you try to define a field with the same name again without explicitly overriding it.

    Look at the simpler example:

    trait A { val v = "a" }
    trait B { this: A =>
      var v = "b"
    }
    
    new A with B {} // does not compile
    
    <console>:9: error: overriding value v in trait A$class of type java.lang.String;
     variable v in trait B$class of type java.lang.String needs `override' modifier
       new A with B {}
    

    So, even though, you don’t get an error in defining B, you’re simply not able to use it anyway.

    This would work

    trait A { val v = "a" }
    trait B { this:A => override val v = "b"  }
    
    new A with B {}
    

    Here, you are explicitly overriding A’s v in B. (Note that new B with A {} would fail because B needs to come last.) Also, it has to be a val because in most cases you cannot really override vars and you cannot override something else using a var.

    Generally, you should not be concerned about the name of the self-type in these simple cases. As long as you do not create another trait or class inside B, both this and whatever you’d call your self-type variable will point to the same thing. There’ll be no shadowing. If you had a new trait inside B, and you needed to refer to the instance of B inside that trait, you’d need another name for your self-type.

    Consider this

    trait B { this =>
      val v = "b"
      trait C {
        val v = "c"
        println(this.v)
      }
      new C {}
    }
    new B {}
    
    // prints c
    

    vs this:

    trait B { self =>
      val v = "b"
      trait C {
        val v = "c"
        println(this.v)  // prints c
        println(self.v)  // prints b
      }
      new C {}
    }
    new B {}
    

    (Without any further type annotation, you could leave out all of the this in this example.)