我正在使用Scala 2.10.4。
请打个比方 - 实际的代码深深地嵌入在一个复杂的程序中,所以我不会解释这个问题,而是以一种历史悠久的方式来描述动物的问题; - )
在scala中我有两个特征 - 例如:
动物和 HouseBase 。
我无法更改Animal,但我继承了Dog,Rabbit,Fish等类。令人讨厌的是,我无法更改每个子类,因为我没有拥有我使用的所有子类。
我的动物都住在某个地方 - 他们的家必须从HouseBase继承。我可以改变HouseBase和它的子类(通过另一层抽象,如果必须的话)。
所以Dog是Animal的子类,并且会生活在一个Kennel中,它是HouseBase的子类。
一只兔子会住在一个箱子里,而一只鱼则住在坦克里。
注意这里没有强制执行1:1的关系 - 鱼也可以住在池塘里,我们也必须能够处理它。
我希望的是 - 给定一个具体的动物(例如Fish),通过抽象类型Animal引用,并给定一个具体的返回类型(例如Tank),Scala将能够自动选择正确的我在下面的设计中隐式参数。
object AnimalSelectionProblem extends App {
def abstractFish : Animal = new Fish(true, 20.0)
def concreteFish : Fish = new Fish(false, 30.0)
def abstractDog : Animal = new Dog("tasty bone")
def concreteDog : Dog = new Dog("yummy bone")
def abstractRabbit : Animal = new Rabbit(5)
def concreteRabbit : Rabbit = new Rabbit(10)
import HouseImplicits._
val myTank1: Tank = HouseImplicits.create(abstractFish)
val myTank2: Tank = HouseImplicits.create(concreteFish)
val myKennel1: Kennel = HouseImplicits.create(abstractDog)
val myKennel2: Kennel = HouseImplicits.create(concreteDog) // This works
val myhutch1: Hutch = HouseImplicits.create(abstractRabbit)
val myhutch2: Hutch = HouseImplicits.create(concreteRabbit) // This works
}
然而,有2个相关问题。
问题1 - 如果动物被引用为摘要,则隐式参数将仅查找采用抽象类型(Animal)而不是基础具体类型的函数。我怀疑解决方案可能是使用ClassTags,因为Scala似乎没有使用运行时信息?我实现了这个目标并且失去了绝望(我对Scala来说还是新手!)。
问题2 - 如果我的动物可以生活在多种类型的House中,那么会出现类似的问题,即使指定了具体的返回类型,编译器也会发现Fish的两个隐含对象不明确。我有点难过这里要做什么!
我可以设想使用手动样板的解决方案来匹配运行时的类型,但这不是很容易扩展。
任何想法都感激不尽!其余代码如下。
编辑 - 这些链接似乎证实了我所怀疑的内容。使用了编译时多态性,因此无法知道运行时类型:
http://like-a-boss.net/2013/03/29/polymorphism-and-typeclasses-in-scala.html
所以,我想我现在的问题是,鉴于此,是否有办法修改我的示例以使用运行时调度?
动物
trait Animal {
}
class Dog(val boneName: String) extends Animal
class Rabbit(val length: Int) extends Animal
class Fish(val likesFrogs: Boolean, val optimumTemp: Double) extends Animal
众议院和暗示:
sealed trait HouseBase
// Made up some arbitrary member variables
case class Kennel(posessions: Seq[String]) extends HouseBase
case class Hutch(length: Int) extends HouseBase
case class Tank(waterTemp: Double) extends HouseBase
case class Pond(containsFrog: Boolean) extends HouseBase
sealed trait HouseCreator[A <: Animal, HB <: HouseBase] {
def create(animal: A): HB
}
object HouseImplicits {
implicit object BuildKennelForDog extends HouseCreator[Dog, Kennel] {
override def create(dog: Dog): Kennel = {
new Kennel(Seq(dog.boneName))
}
}
implicit object BuildTankForFish extends HouseCreator[Fish, Tank] {
override def create(fish: Fish): Tank = {
new Tank(fish.optimumTemp)
}
}
implicit object BuildPondForFish extends HouseCreator[Fish, Pond] {
override def create(fish: Fish): Pond = {
new Pond(fish.likesFrogs)
}
}
implicit object BuildHutchForRabbit extends HouseCreator[Rabbit, Hutch] {
override def create(rabbit: Rabbit): Hutch = {
new Hutch(rabbit.length*5)
}
}
def create[A <: Animal, H <: HouseBase](animal: A)(implicit house: HouseCreator[A,H]) : H = {
val newHouse = house.create(animal)
newHouse
}
}
答案 0 :(得分:2)
所以基本上你想要以下设计:
HouseBase
的具体类型是已知的。Animal
的具体类型不已知。HouseBase
提供的运行时动物数据创建特定类型Animal
。Animal
实施,也不想更改HouseBase
实施。理想的事情当然是在编译时可以使用Animal
的具体类型。由于似乎有一些相关知识(您知道在编译时为动物变量创建哪个HouseBase
),您可以尝试使用shapeless
中的type-safe cast来获取Option
1}}具体的Animal
类型。
但如果不可能,你必须使用动物的运行时调度。
在这种情况下,我认为方法create
应具有以下签名:
def create[HB <: HouseBase](animal: Animal): Option[HB]
您知道HouseBase
的具体类型,因此您也可以将其作为类型参数传递,并且返回值为Option
,以说明所提供动物的类型可能不匹配适用于具体HouseBase
实现这一目标的一种可能方法是使用单个对象的以下代码,该对象具有HouseBase
的{{1}} s生成的所有知识(也应该可以实现同样的事情)通过将创建代码移动到具体Animal
s)的伴随对象中:
HouseBase
然后你可以这样调用这些函数:
sealed trait HouseCreator[HB <: HouseBase] {
def create(animal: Animal): Option[HB]
}
object HouseCreator {
implicit object KennelCreator extends HouseCreator[Kennel] {
def create(animal: Animal): Option[Kennel] = animal match {
case dog: Dog => Some(Kennel(Seq(dog.boneName)))
case _ => None
}
}
implicit object HutchCreator extends HouseCreator[Hutch] {
def create(animal: Animal): Option[Hutch] = animal match {
case rabbit: Rabbit => Some(Hutch(rabbit.length * 5))
case _ => None
}
}
implicit object TankCreator extends HouseCreator[Tank] {
def create(animal: Animal): Option[Tank] = animal match {
case fish: Fish => Some(Tank(fish.optimumTemp))
case _ => None
}
}
implicit object PondCreator extends HouseCreator[Pond] {
def create(animal: Animal): Option[Pond] = animal match {
case fish: Fish => Some(Pond(fish.likesFrogs))
case _ => None
}
}
def create[HB <: HouseBase : HouseCreator](animal: Animal): Option[HB] =
implicitly[HouseCreator[HB]].create(animal)
}
此外,使用val myTank1: Option[Tank] = HouseCreator.create[Tank](abstractFish)
val myTank2: Option[Tank] = HouseCreator.create[Tank](concreteFish)
// Types of the variables can also be inferred automatically
val myKennel1 = HouseCreator.create[Kennel](abstractDog)
val myKennel2 = HouseCreator.create[Kennel](concreteDog)
val myhutch1 = HouseCreator.create[Hutch](abstractRabbit)
val myhutch2 = HouseCreator.create[Hutch](concreteRabbit)
s可以减少HouseCreator
中的样板代码:
PartialFunction
答案 1 :(得分:0)
当你的子类被静态地声明为其超类的一个实例时,你想要的是推断子类的运行时类型。这显然是不可能的,所以除非你希望赢得某种计算机科学奖,否则不要试图让它发挥作用!
您可以将其编写为使用单个HouseCreator
方法接受类型为create()
的对象,而不是参数化Animal
类。它可以使用基于House
的运行时子类型匹配的大小写匹配来创建适当的Animal
。
sealed trait HouseCreator {
def create(animal: Animal): HouseBase {
animal match {
case dog: Dog => new Kennel(Seq(dog.boneName))
case fish: Fish => // etc...
}
}
}
这只能返回一个HouseBase
对象而不是一个特定的子类(至少我在这里实现它)。您也可以始终匹配返回值。