Reputation: 858
I have the following class hierarchy.
sealed trait Foo {
val a: String
}
case class Bar1(a: String) extends Foo
case class Bar2(a: String) extends Foo
Now I want to add a convenient method to modify the field a
. I need this method to be accessible in the super type Foo
and I want to use the .copy
method of the case class (because I actually have a lot more fields and it is painful to use the constructor). My first attempt was to use pattern matching :
sealed trait Foo {
val a: String
def withField(b: String) = this match {
case b1: Bar1 => b1.copy(a = b)
case b2: Bar2 => b2.copy(a = b)
}
}
Now I would also like my withField
method to return an instance type of the caller, B1
if the method is called by an instance of type B1
, B2
if the method is called by an instance of type B2
and Foo
if this is all I know. So I thought to myself I might be able to parametrized the method withField
to serve this purpose. Something like:
sealed trait Foo {
val a: String
def withField[A <: Foo](b: String) = this match {
case b1: Bar1 => b1.copy(a = b)
case b2: Bar2 => b2.copy(a = b)
}
}
but I don't manage to parametried withField
with the type of this
.
Am I getting completely wrong here ? Should I use a different pattern maybe using override
modifier ?
Thanks a lot
Upvotes: 1
Views: 182
Reputation: 170723
Am I getting completely wrong here ? Should I use a different pattern maybe using override modifier ?
Yes. There are two alternatives:
sealed trait Foo {
val a: String
def withField(b: String): Foo
}
case class Bar1(a: String) extends Foo {
// return types are covariant, and Bar1 is subtype of Foo,
// so this is legal
def withField(b: String): Bar1 = ...
}
or
sealed trait Foo[ThisType <: Foo[ThisType]] {
val a: String
def withField(b: String): ThisType
}
case class Bar1(a: String) extends Foo[Bar1] {
def withField(b: String): Bar1 = ...
}
Note the second is more complex, and should only be used if you actually need it.
EDIT to answer Christian's question:
sealed trait Foo {
type ThisType <: Foo
def withField(b: String): ThisType = (this match {
case b1: Bar1 => b1.copy(a = b)
...
}).asInstanceOf[ThisType]
}
case class Bar1(a: String) extends Foo {
type ThisType = Bar1
}
I don't like it: it needs a cast, actually using it would require dependent method types, and I wouldn't be surprised if it broke down in practice (e.g. because the compiler can't prove foo.ThisType
and foo.withField("a").ThisType
are the same).
Upvotes: 2