提问人:Random42 提问时间:8/23/2021 最后编辑:zmerrRandom42 更新时间:9/16/2021 访问量:161
如何替代通用匿名函数?
How to substitute generic anonymous functions?
问:
假设有腿动物有一个特征:
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
答:
这可以使用以下方法完成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上试试吧。
我认为您可以通过拥有一个真正的通用方法(带有类型参数)来避免遇到的大多数问题:mutate
def mutate[T <: Legged](legged: T): T = legged.updateLegs(legged.legs + 1)
然后,当应用于 a 时,它将返回 a ,同样适用于 。Chicken
Chicken
Dog
评论
mutate
mapAll
一种可能的方法可以做到这一点(安全地键入,不使用 )可以使用与对象相关的类型。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)
}
通过这种方式,可以定义一个函数来返回传递的具体子类(类似于 @Gaël J 所做的):Legged
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)
}
}
Scastie for Scala 2
但为什么是对象依赖型呢?
在 Scala 3.0 中,可以定义为:dependent function type
type LeggedFunc = (l: Legged) => l.Me
val mutate: LeggedFunc = (l) => l.updateLegs(l.legs + 1)
使此解决方案(与对象相关的类型)更简洁且类型安全。
Scastie for Scala 3 版本
我只想补充一下 @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))
评论