Suppose there is a trait for legged animals:
trait Legged {
val legs: Int
def updateLegs(legs: Int): Legged
}
And there two such legged animals:
case class Chicken(feathers: Int, legs: Int = 2) extends Legged {
override def updateLegs(legs: Int): Legged = copy(legs = legs)
}
case class Dog(name: String, legs: Int = 4) extends Legged {
override def updateLegs(legs: Int): Legged = copy(legs = legs)
}
There is also a holder for these animal, in a farm
case class Farm(chicken: Chicken, dog: Dog)
And a generic method to mutate all the legged animals by adding them one extra leg
def mutate(legged: Legged): Legged = legged.updateLegs(legged.legs + 1)
The question is how to implement a method on the Farm
so that it takes the mutate: Legged => Legged
function as a parameter and applies it to all the Legged
animals?
val farm = Farm(Chicken(1500), Dog("Max"))
farm.mapAll(mutate) //this should return a farm whose animals have an extra leg
What I've come with thus far, but it doesn't actually work
trait LeggedFunc[T <: Legged] extends (T => T)
case class Farm(chicken: Chicken, dog: Dog) {
def mapAll(leggedFunc: LeggedFunc[Legged]): Farm = {
//todo how to implement?
val c = leggedFunc[Chicken](chicken)
}
}
I know how to do it with patter matching, but that leads to potential MatchError
.
A possible way to do that (type-safely, without using asInstanceOf
) could be using object-dependant type.
First of all, we should add an abstract member that uses the concrete type of Legged
subclasses:
sealed trait Legged { self =>
type Me >: self.type <: Legged // F-Bounded like type, Me have to be the same type of the subclasses
val legs: Int
def updateLegs(legs: Int): Me
}
Then, the Legged
subclasses became:
case class Chicken(feathers: Int, legs: Int = 2) extends Legged {
type Me = Chicken
override def updateLegs(legs: Int): Chicken = copy(legs = legs)
}
case class Dog(name: String, legs: Int = 4) extends Legged {
type Me = Dog
override def updateLegs(legs: Int): Dog = copy(legs = legs)
}
In this way, it is possible to define a function that returns the concrete subclass of the Legged
passed (similar to what @Gaël J done):
trait LeggedFunc {
def apply(a : Legged): a.Me
}
val mutate = new LeggedFunc { override def apply(legged: Legged): legged.Me = legged.updateLegs(legged.legs + 1) }
Finally, the Farm
class is straightforward defined as:
case class Farm(chicken: Chicken, dog: Dog) {
def mapAll(leggedFunc: LeggedFunc): Farm = {
val c : Chicken = leggedFunc(chicken)
val d : Dog = leggedFunc(dog)
Farm(c, d)
}
}
Scastie for Scala 2
But why object-dependent type?
In Scala 3.0, it is possible to define dependent function type
as:
type LeggedFunc = (l: Legged) => l.Me
val mutate: LeggedFunc = (l) => l.updateLegs(l.legs + 1)
Making this solution (object-dependent type) cleaner and type-safe.
Scastie for Scala 3 version
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With