4

假设有腿动物有一个特征:

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 mutate(legged: Legged): Legged = legged.updateLegs(legged.legs + 1)

问题是如何实现一个方法,Farm以便将mutate: Legged => Legged函数作为参数并将其应用于所有Legged动物?

val farm = Farm(Chicken(1500), Dog("Max"))
farm.mapAll(mutate) //this should return a farm whose animals have an extra leg

到目前为止我所带来的,但它实际上并没有工作

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

我知道如何通过模式匹配来做到这一点,但这会导致潜在的MatchError.

4

4 回答 4

6

一种可能的方法(类型安全,不使用asInstanceOf)可能是使用对象相关类型。

首先,我们应该添加一个使用Legged子类的具体类型的抽象成员:

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
}

然后,Legged子类变为:


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

通过这种方式,可以定义一个返回Legged传递的具体子类的函数(类似于@Gaël J 所做的):

trait LeggedFunc {
  def apply(a : Legged): a.Me
}

val mutate = new LeggedFunc { override def apply(legged: Legged): legged.Me = legged.updateLegs(legged.legs + 1) }

最后,Farm该类直接定义为:

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

Scala 2 的Scastie

但是为什么要依赖对象类型呢?在 Scala 3.0 中,可以定义dependent function type为:

type LeggedFunc = (l: Legged) => l.Me
val mutate: LeggedFunc = (l) => l.updateLegs(l.legs + 1)

使这个解决方案(依赖于对象的类型)更清晰和类型安全。

Scala 3 版本的Scastie

于 2021-08-23T08:31:43.927 回答
2

我将添加到@gianlucaaguzzi的答案中,即在 Scala 2 中,依赖/多态函数可以用 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))
于 2021-08-23T21:56:58.670 回答
0

这可以使用方法来完成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))

scastie上试试。

更新:这是一个更类似于您自己的代码的替代实现:

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

scastie上试试。

于 2021-08-23T07:25:29.797 回答
0

我认为您可以通过使用真正通用的mutate方法(带有类型参数)来避免遇到的大多数问题:

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

然后,当应用于 aChicken时,它将返回 a Chicken,同样适用于Dog

于 2021-08-23T07:45:39.970 回答