4

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.

zmerr
  • 534
  • 3
  • 18
Random42
  • 8,989
  • 6
  • 55
  • 86

4 Answers4

6

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

gianluca aguzzi
  • 1,734
  • 1
  • 10
  • 22
2

I'll just add to @gianlucaaguzzi's answer that in Scala 2 dependent/polymorphic functions can be emulated with Shapeless

import shapeless.ops.hlist.Mapper
import shapeless.{Generic, HList, Poly1}

case class Farm(chicken: Chicken, dog: Dog) {
  def mapAll[L <: HList](mutate: Poly1)(implicit
    generic: Generic.Aux[Farm, L],
    mapper: Mapper.Aux[mutate.type, L, L]
  ): Farm = generic.from(mapper(generic.to(this)))
}

object mutate extends Poly1 {
  implicit def cse[T <: Legged]: Case.Aux[T, T#Me] = 
    at(legged => legged.updateLegs(legged.legs + 1))
}

val farm = Farm(Chicken(1500), Dog("Max"))
println(farm.mapAll(mutate)) // Farm(Chicken(1500,3),Dog(Max,5))
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
0

This can be done using the method asInstanceOf

trait Legged {
  val legs: Int

  def updateLegs(legs: Int): Legged
}

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)
}

case class Farm(chicken: Chicken, dog: Dog){
  
  def mapAll(leggedFunc: (Legged) => Legged): Farm = {
   
      copy(
           leggedFunc(chicken.asInstanceOf[Legged]).asInstanceOf[Chicken], 
           leggedFunc(dog.asInstanceOf[Legged]).asInstanceOf[Dog]
          )

   } 
}

def mutate(legged: Legged): Legged = legged.updateLegs(legged.legs + 1)

val farm = Farm(Chicken(1500), Dog("Max"))


println (farm.mapAll(mutate)) // prints: Farm(Chicken(1500,3),Dog(Max,5))

Try it on scastie.

Update: This is an alternative implementation more similar to your own code:

trait LeggedFunc[T <: Legged] extends (T => T)


case class Farm(chicken: Chicken, dog: Dog) {
  def mapAll(leggedFunc: LeggedFunc[ Legged]): Farm = {
    val c = leggedFunc(chicken).asInstanceOf[Chicken]
    val d = leggedFunc(dog).asInstanceOf[Dog]
    copy (c, d)
  }
}

Try it on scastie.

zmerr
  • 534
  • 3
  • 18
0

I think you'd avoid most of the issues you encounter by having a truly generic mutate method (with a type parameter):

def mutate[T <: Legged](legged: T): T = legged.updateLegs(legged.legs + 1)

Then, when applied to a Chicken it will return back a Chicken, and same goes for Dog.

Gaël J
  • 11,274
  • 4
  • 17
  • 32
  • When I was trying the code on scastie, the errors were in the function definitions, not in the final application of `mutate`, when it was passed to `mapAll`. – zmerr Aug 23 '21 at 08:14
  • The problem in this is case is which type parameter is applied when calling the method on the farm? – Random42 Aug 24 '21 at 13:40