0
class S {
  case class A(a: Int)
}

abstract class R(val s: S) {
  type T1 = R.this.s.A
  type T2 = s.A
  implicitly[T1 =:= T2] // compiles

  type T3 = R.this.type
  type T4 = this.type
  implicitly[T3 =:= T4] // compiles

  val v1 = R.this //  v1 == `this`
  val v2 = R.this.s // v2 == `s`
}

Looks like the .this part has no effect whatsoever. To make a concrete question: When do you use the .this ?

Ashkan Kh. Nazary
  • 21,844
  • 13
  • 44
  • 68
  • 1
    As in **Java** `this` is completely optional when is not ambiguous. The `R` is even lesser common since that is only needed when you need to disambiguate which parent class you want to access. – Luis Miguel Mejía Suárez Jun 28 '21 at 13:56
  • @LuisMiguelMejíaSuárez Would you then put some examples in an answer that clearly shows where it *is* ambiguous or more clear to use it ? – Ashkan Kh. Nazary Jun 28 '21 at 14:01
  • Sorry I am on cellphone and writing code is a nightmare. The classical example is a hand made setter which are very very weird. I sometimes like to use `this` when I receive another instance of the same class, while is not needed at all I like to make it more verbose and clear. - Hope someone will post a complete answer latter. – Luis Miguel Mejía Suárez Jun 28 '21 at 14:23

2 Answers2

3

It matters for inner classes. E.g.

class Outer(val x: Int) {
  class Inner(val x: Int) {
    def outer_this = Outer.this.x
    def inner_this = this.x // or just x
  }
}

val outer = new Outer(0)
val inner = new outer.Inner(1)
println(inner.outer_this) // 0
println(inner.inner_this) // 1

Each Outer.Inner instances "belongs to" a specific Outer instance, and can refer to that instance as Outer.this. By itself x in Inner refers to its own property, so if you need the enclosing instance's x property, you write Outer.this.x.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
2

One rule is Scala never infers singleton type this.type. For example first consider the mechanics of it

scala> trait Foo {
     |   type T
     |   def f() = this // we left out the return type to see what Scala will infer
     | }
// defined trait Foo

scala> new Foo { type T = String }
val res0: Foo{T = String} = anon$1@6d3ad37a

scala> res0.f()
val res1: Foo = anon$1@6d3ad37a

Note how res1 has return type of Foo and not Foo { type T = String }, so we have lost some type information

scala> val x: res1.T = ""
1 |val x: res1.T = ""
  |                ^^
  |                Found:    ("" : String)
  |                Required: res1.T

Note compiler does not know res1.T is actually a String. So compiler did not infer the singleton type this.type which would have all the type information including what type member T was instantiated to

scala> trait Foo {
     |   type T
     |   def f(): this.type = this
     | }
// defined trait Foo

scala> new Foo { type T = String }
val res2: Foo{T = String} = anon$1@7d381eae

scala> res2.f()
val res3: Foo{T = String} = anon$1@7d381eae

scala> val x: res3.T = ""
val x: res3.T = ""

Note how after we explicitly declared singleton return type this.type compiler knows T is a String.

Here is another mechanical example of what happens because compiler does not infer singleton type this.type

scala> trait Foo {
     |   def f() = this // let inference do its thing
     | }
// defined trait Foo

scala> trait Bar {
     |   def g() = 42
     | }
// defined trait Bar

scala> trait Bar extends Foo {
     |   def g(): Int = 42
     | }
// defined trait Bar

scala> new Bar {}
val res5: Bar = anon$1@6a9a6a0c

scala> res5.f()
val res6: Foo = anon$1@6a9a6a0c

scala> res6.g()
1 |res6.g()
  |^^^^^^
  |value g is not a member of Foo

Note how f() call typed to Foo and not perhaps expected Bar. On the other hand if we provide explicit singleton return type this.type then

scala> trait Foo {
     |   def f(): this.type = this
     | }
// defined trait Foo

scala> trait Bar extends Foo {
     |   def g(): Int = 42
     | }
// defined trait Bar

scala> new Bar {}
val res7: Bar = anon$1@4707d60a

scala> res7.f()
val res8: Bar = anon$1@4707d60a

scala> res8.g()
val res9: Int = 42

we see f() call typed to Bar.

Those were the mechanics but what about practical applications? Two uses I am aware of are:

  • supporting fluent style of programming
  • supporting summoners of type class instances (see also Scala 3 summon)

Mario Galic
  • 47,285
  • 6
  • 56
  • 98