Reputation: 1250
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 ?
Upvotes: 2
Views: 1209
Reputation: 22895
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.
Upvotes: 1
Reputation: 44967
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 e.g. IterableLike as a typical example of such F-bounded polymorphism.
Upvotes: 2
Reputation: 40510
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
).
Upvotes: 6
Reputation: 1250
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.
Upvotes: 1