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)