Scala - 抽象类型和隐式参数解析
Scala - Abstract types and Implicit Parameter Resolution
我正在使用 Scala 2.10.4。
请打个比方 - 实际代码深深地嵌入到一个复杂的程序中,所以与其解释它,我将以一种历史悠久的方式抽象问题来谈论动物 ;-)
在 scala 中,我有 2 个特征 - 例如:
动物,和HouseBase。
我无权更改 Animal,但我从它那里继承了 类,例如 Dog、Rabbit、Fish。恼人的是,我不能更改每个子类,因为我不拥有我使用的所有子类。
我的动物都住在某个地方 - 它们的家必须继承自 HouseBase。我可以更改 HouseBase,它是 sub类(如果必须的话,通过另一层抽象)。
所以 Dog 是 Animal 的子类,并且会住在 Kennel 中,Kennel 是 HouseBase 的子类。
兔子住在笼子里,鱼住在鱼缸里。
请注意,这里没有强制执行 1:1 关系 - 鱼也可以生活在池塘中,我们也必须能够处理这个问题。
我希望的是——给定一个具体的动物(例如 Fish),它通过抽象类型 Animal 引用,并给定一个具体的 return 类型(例如 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 - 如果我的动物可以居住在不止一种类型的房屋中,则会出现类似的问题,即即使指定了具体的 return 类型,编译器也会为 Fish 找到 2 个隐式对象模糊的。我有点不知所措!
我可以想出带有手动样板的解决方案来匹配运行时的类型,但这不是很可扩展。
感谢收到任何想法!其余代码如下。
编辑 - 这些链接似乎证实了我的怀疑。使用了编译时多态性,因此无法知道运行时类型:
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
}
}
你想要的是当你的子class被静态声明为它的超class的一个实例时,编译器推断出你的子class的运行时间类型。事实证明这是不可能的,所以除非你希望赢得某种计算机科学奖,否则不要试图让它发挥作用!
您可以将 HouseCreator
class 参数化,而不是将其编写为具有单个 create()
方法来接受类型 Animal
的对象。它可以使用基于 Animal
.
的 运行 时间子类型匹配的大小写匹配来创建适当的 House
sealed trait HouseCreator {
def create(animal: Animal): HouseBase {
animal match {
case dog: Dog => new Kennel(Seq(dog.boneName))
case fish: Fish => // etc...
}
}
}
这只能 return 一个 HouseBase
对象而不是一个特定的子对象 class(至少我在这里实现的是这样)。您也可以始终大小写匹配 return 值。
所以基本上你想要以下设计:
- 在编译时
HouseBase
的具体类型是已知的。
- 在编译时
Animal
的具体类型 未知。
- 为
Animal
提供的 运行time 动物数据创建特定类型 HouseBase
。
- 无法更改
Animal
实施,并且真的不想更改 HouseBase
实施。
理想的事情当然是在编译时提供 Animal
的具体类型。由于似乎对此有一些了解(您知道在编译时为动物变量创建哪个 HouseBase
),您可以尝试使用 shapeless
中的 type-safe cast 来获得 Option
具体 Animal
类型。
但如果不可能,您必须使用 运行 时间派遣动物。
在那种情况下,我认为方法 create
应该具有以下签名:
def create[HB <: HouseBase](animal: Animal): Option[HB]
您知道 HouseBase
的具体类型,因此您也可以将其作为类型参数传递,并且 return 值是 Option
以说明提供的动物类型和具体的动物类型 HouseBase
一种可能的实现方式是以下代码,其中包含一个对象,该对象具有从 Animal
生成 HouseBase
的所有知识(也应该可以实现通过将创建代码移动到具体 HouseBase
s):
的伴随对象中,同样的事情
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)
此外,HouseCreator
中的样板代码可以通过使用 PartialFunction
s 来减少:
sealed trait HouseCreator[HB <: HouseBase] {
def create: PartialFunction[Animal, HB]
}
object HouseCreator {
implicit object KennelCreator extends HouseCreator[Kennel] {
def create = {
case dog: Dog => Kennel(Seq(dog.boneName))
}
}
implicit object HutchCreator extends HouseCreator[Hutch] {
def create = {
case rabbit: Rabbit => Hutch(rabbit.length * 5)
}
}
implicit object TankCreator extends HouseCreator[Tank] {
def create = {
case fish: Fish => Tank(fish.optimumTemp)
}
}
implicit object PondCreator extends HouseCreator[Pond] {
def create = {
case fish: Fish => Pond(fish.likesFrogs)
}
}
def create[HB <: HouseBase : HouseCreator](animal: Animal): Option[HB] =
implicitly[HouseCreator[HB]].create.lift(animal)
}
我正在使用 Scala 2.10.4。
请打个比方 - 实际代码深深地嵌入到一个复杂的程序中,所以与其解释它,我将以一种历史悠久的方式抽象问题来谈论动物 ;-)
在 scala 中,我有 2 个特征 - 例如:
动物,和HouseBase。
我无权更改 Animal,但我从它那里继承了 类,例如 Dog、Rabbit、Fish。恼人的是,我不能更改每个子类,因为我不拥有我使用的所有子类。
我的动物都住在某个地方 - 它们的家必须继承自 HouseBase。我可以更改 HouseBase,它是 sub类(如果必须的话,通过另一层抽象)。
所以 Dog 是 Animal 的子类,并且会住在 Kennel 中,Kennel 是 HouseBase 的子类。
兔子住在笼子里,鱼住在鱼缸里。
请注意,这里没有强制执行 1:1 关系 - 鱼也可以生活在池塘中,我们也必须能够处理这个问题。
我希望的是——给定一个具体的动物(例如 Fish),它通过抽象类型 Animal 引用,并给定一个具体的 return 类型(例如 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 - 如果我的动物可以居住在不止一种类型的房屋中,则会出现类似的问题,即即使指定了具体的 return 类型,编译器也会为 Fish 找到 2 个隐式对象模糊的。我有点不知所措!
我可以想出带有手动样板的解决方案来匹配运行时的类型,但这不是很可扩展。
感谢收到任何想法!其余代码如下。
编辑 - 这些链接似乎证实了我的怀疑。使用了编译时多态性,因此无法知道运行时类型:
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
}
}
你想要的是当你的子class被静态声明为它的超class的一个实例时,编译器推断出你的子class的运行时间类型。事实证明这是不可能的,所以除非你希望赢得某种计算机科学奖,否则不要试图让它发挥作用!
您可以将 HouseCreator
class 参数化,而不是将其编写为具有单个 create()
方法来接受类型 Animal
的对象。它可以使用基于 Animal
.
House
sealed trait HouseCreator {
def create(animal: Animal): HouseBase {
animal match {
case dog: Dog => new Kennel(Seq(dog.boneName))
case fish: Fish => // etc...
}
}
}
这只能 return 一个 HouseBase
对象而不是一个特定的子对象 class(至少我在这里实现的是这样)。您也可以始终大小写匹配 return 值。
所以基本上你想要以下设计:
- 在编译时
HouseBase
的具体类型是已知的。 - 在编译时
Animal
的具体类型 未知。 - 为
Animal
提供的 运行time 动物数据创建特定类型HouseBase
。 - 无法更改
Animal
实施,并且真的不想更改HouseBase
实施。
理想的事情当然是在编译时提供 Animal
的具体类型。由于似乎对此有一些了解(您知道在编译时为动物变量创建哪个 HouseBase
),您可以尝试使用 shapeless
中的 type-safe cast 来获得 Option
具体 Animal
类型。
但如果不可能,您必须使用 运行 时间派遣动物。
在那种情况下,我认为方法 create
应该具有以下签名:
def create[HB <: HouseBase](animal: Animal): Option[HB]
您知道 HouseBase
的具体类型,因此您也可以将其作为类型参数传递,并且 return 值是 Option
以说明提供的动物类型和具体的动物类型 HouseBase
一种可能的实现方式是以下代码,其中包含一个对象,该对象具有从 Animal
生成 HouseBase
的所有知识(也应该可以实现通过将创建代码移动到具体 HouseBase
s):
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)
此外,HouseCreator
中的样板代码可以通过使用 PartialFunction
s 来减少:
sealed trait HouseCreator[HB <: HouseBase] {
def create: PartialFunction[Animal, HB]
}
object HouseCreator {
implicit object KennelCreator extends HouseCreator[Kennel] {
def create = {
case dog: Dog => Kennel(Seq(dog.boneName))
}
}
implicit object HutchCreator extends HouseCreator[Hutch] {
def create = {
case rabbit: Rabbit => Hutch(rabbit.length * 5)
}
}
implicit object TankCreator extends HouseCreator[Tank] {
def create = {
case fish: Fish => Tank(fish.optimumTemp)
}
}
implicit object PondCreator extends HouseCreator[Pond] {
def create = {
case fish: Fish => Pond(fish.likesFrogs)
}
}
def create[HB <: HouseBase : HouseCreator](animal: Animal): Option[HB] =
implicitly[HouseCreator[HB]].create.lift(animal)
}