Reputation: 17026
I have an abstract Scala class Base
which has subclasses Derived1
and Derived2
. Base
defines a function f() which returns an object of the same type as its implementing class. So Derived1.f()
returns Derived1
and Derived2.f()
returns Derived2
. How do I write this in Scala?
Here is what I have come up with so far.
package com.github.wpm.cancan
abstract class Base {
def f[C <: Base]: C
}
case class Derived1(x: Int) extends Base {
def f[Derived1] = Derived1(x + 1)
}
case class Derived2(x: Int) extends Base {
def f[Derived2] = Derived2(x + 2)
}
This gives the following compiler errors:
type mismatch;
[error] found : com.github.wpm.cancan.Derived1
[error] required: Derived1
[error] def f[Derived1] = Derived1(x + 1)
type mismatch;
[error] found : com.github.wpm.cancan.Derived2
[error] required: Derived2
[error] def f[Derived2] = Derived2(x + 2)
This error message is confusing to me because I think com.github.wpm.cancan.Derived1
should be the same as Derived1
in this context.
Upvotes: 8
Views: 6057
Reputation: 3120
Just to add a small precision about (perfectly good) Travis Brown answer: It's not that C
in trait Base[C <: Base[C]]
let's you refer to the implementing class; it's just sticking to the convention of writing subclass extends Base[subclass]
that let's you do so. There's no way that I know of to refer to this type. To clarify what I mean, this compiles
trait Base[C <: Base[C]] { def f: C }
case class Derived1(x: Int) extends Base[Derived1] {
def f: Derived1 = Derived1(x + 1)
}
// a Derived2 where f returns Derived1!!
case class Derived2(x: Int) extends Base[Derived1] {
def f = Derived1(x + 2)
}
Now, if all you're going to have as implementations of Base
are case classes, you can get this right through a self-type bound:
trait Base[C <: Base[C]] { self: C =>
def f: C
}
case class Derived1(x: Int) extends Base[Derived1] {
def f: Derived1 = Derived1(x + 1)
}
// a Derived2 where f returns Derived1!!
// this won't compile now
case class Derived2(x: Int) extends Base[Derived1] {
def f = Derived1(x + 2)
}
Upvotes: 8
Reputation: 139028
Randall Schulz pointed out one of the reasons your current code doesn't work. It is possible to get what you want, though, with F-bounded polymorphism:
trait Base[C <: Base[C]] { def f: C }
case class Derived1(x: Int) extends Base[Derived1] {
def f: Derived1 = Derived1(x + 1)
}
case class Derived2(x: Int) extends Base[Derived2] {
// Note that you don't have to provide the return type here.
def f = Derived2(x + 2)
}
The type parameter on the base trait allows you to talk about the implementing class there—e.g. in the return type for f
.
Upvotes: 15