简体   繁体   中英

Scala: Maintain child class in parent methods?

When you have a parent:

abstract class Parent {
   def something(arg: ???): Parent = ???
}

and

class Child extends Parent {}

I would like

val updatedChild = new Child().something(...)

updatedChild to be of type Child and not of type Parent , is it possible ?

One way to do it, is to parametrize the parent:

 abstract class Parent[T <: Parent[T]] {
    def something(arg: Foo): T
 }

 class Child(val foo: String) extends Parent[Child] {
    def something(arg: String) = return new Child(arg)
 }

Sometimes, you can also get away with using this.type :

class Parent {
  def something(arg: Foo): this.type = this
}
class Child {
   override def something(arg: Foo) = this
}

But the latter method only works if all you ever want to return is this ( this.type is not Parent or Child , but a specific type that only has one instance - this ).

Here is a proposal that actually compiles :

abstract class Parent[Repr <: Parent[Repr]] {
  def something(arg: Int): Repr
}

This is something you can do, at least it's not explicitly discouraged. Standard collection library uses it a lot, see eg IterableLike as a typical example of such F-bounded polymorphism.

It seems that you can do :

class Parent[THIS <: Parent[THIS]] {
   def something: THIS
}

And that seems to work.

I am not sure if this is something you should do tho.

Both Andrey's and Dima's answers cover one way to solve the problem using only oo-patterns.

However I would like to point out another approach called typeclasses (which is more common in functional languages) , that would be helpful if you are planning to write generic functions using your interface.

First, instead of having a parent class, you have an interface that describes the operations that can be performed on instances of the typeclass.

trait Typeclass[T] {
  def something(t: T)(arg: Foo): T
}

Then, you would define your types, this time they don't extend any parent class, thus they don't have to override nothing.

class Child {
  ...
}

Now, you have to prove that your type is an instance of the type class.
(A common place to do that is in the companion object of the class) .

object Child {
  implicit final val ChildTypeclass: Typeclass[Child] = new Typeclass[Child] {
    override def something(child: Child)(arg: Foo): Child = ???
  }
}

Finally, you define a generic method that can operate on any type T as long as there is an instance of your typeclass for that type.

def generic[T](t: T, arg: Foo)(implicit tt: Typeclass[T]): T =
  tt.something(t)(arg)

Bonus, if you want to recover the "dot notation" you can add an Ops pattern to your Typeclass.

object syntax {
  object typeclass {
     implicit final class TypeclassOps[T](val t: T) extends AnyVal {
       final def something(arg: Foo)(implicit tt: Typelcass[T]) =
         tt.something(t)(arg)
     }
  }
}

import syntax.typeclasss._

def generic[T: Typelcass](t: T, arg: Foo): T
  t.something(arg)

val newChild = generic(new Child, new Foo)
// newChild: Child = ???

Also, a common approach is to define the something method in your class and the typeclass instance forwards the call to the one defined in the class, this way you can use your method in any instance of Child without having to put all the typeclass machinery.

I must say that this is useful for very high-level abstractions to which you plan to provide instances for many types (even types outside your control like any of the standard collection types) and write very generic functions that can operate on any of these.
If not, F-bounded types seems like the more rational solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM