Why cannot this.type be used for new instances

2020-03-01 06:45发布

问题:

I'd like to be able to use this.type to define a method that makes new instances of an immutable case class. Something like this:

trait Expression
{
  def left : Expression
  def right : Expression

  def new_with_changes(l : Expression, r : Expression) : this.type
}

case class Derived(left : Expression, right : Expression)
{
  def new_with_changes(l : Expression, r : Expression) : this.type =
  {
    new Derived(left, right)
  }
}

Unfortunately, the compiler complains

test.scala:13: error: type mismatch;
 found   : Derived
 required: Derived.this.type
    new Derived(left, right)
    ^
one error found

How come the new case class doesn't match this.type?

If I change this.type to Base in Base.new_with_changes and Derived in Derived.new_with_changes that works, but it seems it's missing out on the nicety of this.type.

Edit: The real intent of the question is why not have an equivalent way in Scala to declare that the caller of the down perform the downcast, much in the same way that this.type does, but for general types. I don't think it would be easy, but it would be nice.

回答1:

[Note: I am not recommending that you do this.] There's a fair chance you can accomplish what you want. The cast to this.type is a lie, but the JVM doesn't know that and can't throw an exception because the singleton type is a scala concept.

Now if you're actually using the singleton property of this.type anywhere, this will get you in trouble in a hurry. But if all you want to do is get covariant return types without all the trouble of typing them, with only the small downside of the huge ugly cast all over the place:

trait Expression
{
  def left : Expression
  def right : Expression

  def new_with_changes(l : Expression, r : Expression) : this.type
}

case class Derived1(left : Expression, right : Expression) extends Expression {
  def new_with_changes(l : Expression, r : Expression) =
    Derived1(left, right).asInstanceOf[this.type]

  def foo() = "Derived1"
}

case class Derived2(left : Expression, right : Expression) extends Expression {
  def new_with_changes(l : Expression, r : Expression) =
    Derived2(left, right).asInstanceOf[this.type]

  def bar() = "Derived2"  
}

And in action:

scala> Derived1(Derived1(null,null), null)
res0: Derived1 = Derived1(Derived1(null,null),null)

scala> res0.new_with_changes(res0, null).bar
<console>:6: error: value bar is not a member of Derived1
       res0.new_with_changes(res0, null).bar
                                         ^

scala> res0.new_with_changes(res0, null).foo
res2: java.lang.String = Derived1

scala> Derived2(Derived2(null, null), null)
res3: Derived2 = Derived2(Derived2(null,null),null)

scala> res3.new_with_changes(null, res3).foo
<console>:6: error: value foo is not a member of Derived2
       res3.new_with_changes(null, res3).foo
                                         ^

scala> res3.new_with_changes(null, res3).bar
res6: java.lang.String = Derived2


回答2:

this.type is the unique type of this particular instance. It's a singleton type - a distinct type from any other instance of the same class. This works

class Foo { def f : this.type = this}

But this does not

class Foo { def f : this.type = new Foo}

this.type isn't needed that often, but it can be used to express some constraints that can't be expressed otherwise

For instance, here the Inner class says each instance's outer method will return the specific Outer instance from which it came.

scala> class Outer{ class Inner { def outer : Outer.this.type = Outer.this}; def f(x : Inner) = println("ok")}
defined class Outer

scala> val o1 = new Outer
o1: Outer = Outer@13c1b69

scala> val o2 = new Outer
o2: Outer = Outer@1a3f178


scala> val in1 = new o1.Inner
in1: o1.Inner = Outer$Inner@627b5c

scala> val in2 = new o2.Inner
in2: o2.Inner = Outer$Inner@158c3b7

scala> val o3 = in1.outer
o3: o1.type = Outer@13c1b69

scala> o1.f(new o3.Inner)  
ok

scala> o1.f(new o2.Inner)
<console>:8: error: type mismatch;
 found   : o2.Inner
 required: o1.Inner
       o1.f(new o2.Inner)

This article has another nice example of using this.type to enable method chaining across subclass boundaries: http://scalada.blogspot.com/2008/02/thistype-for-chaining-method-calls.html

scala>   class A { def method1: this.type = this }
defined class A

scala>   class B extends A { def method2: this.type = this }
defined class B

scala> val b = new B
b: B = B@15cb235

scala> b.method1.method2
res3: b.type = B@15cb235


标签: scala