Final Update/Verdict
Much to my surprise, Scala cannot easily solve such a problem without the use of a 3rd party library. Note that the linked duplicate question does not fulfill the request below.
Original Question
I'm part of a team which recently inherited a Scala project and want to make the code more DRY. I have 2 functions that are identical, but take and return a different case class. Is it possible to have one function take either case class?
I'm looking for a solution using standard Scala without the need to install 3rd party libraries.
I've already tried abstracting the function in many ways with no luck (i.e. using generic types, an Either
type that accepts both case classes, using asInstanceOf
).
Here's a dummy example to illustrate my problem:
trait Bird {
val avgHeight: Int
}
case class Pigeon(avgHeight: Int) extends Bird
case class Ostrich(avgHeight: Int) extends Bird
def updateHeight(bird: ?): ? = {
bird.copy(avgHeight = 2)
}
/*
def updateHeight[T <: Bird](bird: T): T = {
val chosenBird = bird match {
case _: Pigeon => bird.asInstanceOf[Pigeon]
case _: Ostrich => bird.asInstanceOf[Ostrich]
}
chosenBird.copy(avgHeight = 2)
}
*/
println(updateHeight(Pigeon(1)))
println(updateHeight(Ostrich(1)))
Update
Elaborating based on Mario's answer below. How do I remove the need to duplicate the conditional logic?:
sealed trait Bird {
val avgHeight: Int
val avgWidth: Int
val wingSpan: Int
}
case class Pigeon(avgHeight: Int, avgWidth: Int, wingSpan: Int) extends Bird
case class Ostrich(avgHeight: Int, avgWidth: Int, wingSpan: Int) extends Bird
def updateBird(bird: Bird, height: Int, span: Int): Bird = {
bird match {
case p: Pigeon =>
var newPigeon = p.copy(avgHeight = height)
if (p.avgWidth.equals(0)) {
newPigeon = newPigeon.copy(avgWidth = 100)
}
if (span > 0) {
newPigeon = newPigeon.copy(wingSpan = span * 2)
}
newPigeon
case o: Ostrich =>
var newOstrich = o.copy(avgHeight = height)
if (o.avgWidth.equals(0)) {
newOstrich = newOstrich.copy(avgWidth = 100)
}
if (span > 0) {
newOstrich = newOstrich.copy(wingSpan = span * 2)
}
newOstrich
}
}
updateBird(Pigeon(1, 2, 3), 2, 0) // Pigeon(2,2,3)
updateBird(Ostrich(1, 0, 3), 2, 4) // Ostrich(2,100,8)
I'm looking for a truly DRY example where the primary code does not need to be duplicated: Here's an example in Typescript: https://repl.it/repls/PlayfulOverdueQuark
class Bird {
avgHeight: Number
avgWidth: Number
wingSpan: Number
constructor(avgHeight: Number, avgWidth: Number, wingSpan: Number) {
this.avgHeight = avgHeight;
this.avgWidth = avgWidth;
this.wingSpan = wingSpan;
}
}
class Pigeon extends Bird {}
class Ostrich extends Bird {}
const updateBird = (bird: Bird, height: Number, span: Number): Bird => {
const newBird = Object.assign(
bird,
Object.create(
bird instanceof Pigeon
? bird as Pigeon
: bird as Ostrich
)
);
// Update logic only happens below and is not
// duplicated based on the bird type
if (bird.avgWidth === 0) {
// Only update value if X condition is met
// Condition based on passed in object
newBird.avgWidth = 100
}
newBird.avgHeight = height;
if (span > 0) {
// Only update value if X condition is met
newBird.wingSpan = Number(span) * 2;
}
return newBird;
}