如何替代泛型匿名函数?



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

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)可以使用对象依赖类型。

首先,我们应该添加一个抽象成员,使用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)
}
}

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 version

我将添加到@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))

这可以使用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)

然后,当应用于Chicken时,它将返回Chicken,Dog也是如此。

最新更新