【问题标题】:Scala - Abstract types and Implicit Parameter ResolutionScala - 抽象类型和隐式参数解析
【发布时间】:2015-04-13 02:59:29
【问题描述】:

我使用的是 Scala 2.10.4。

请直接打个比方——实际的代码深深嵌入在一个复杂的程序中,所以我不会解释这个问题,而是以一种由来已久的方式来抽象这个问题来谈论动物 ;-)

在 scala 中,我有 2 个特征 - 例如:

动物HouseBase

我无法更改 Animal,但我使用 Dog、Rabbit、Fish 等类从它继承。恼人的是,我无法更改每个子类,因为我不拥有我使用的所有子类。

我的动物都住在某个地方——它们的家必须继承自 HouseBase。我可以更改 HouseBase 及其子类(如果必须的话,可以通过另一层抽象)。

所以 Dog 是 Animal 的子类,并且会生活在作为 HouseBase 子类的 Kennel 中。

兔子会住在笼子里,鱼会住在缸里。

请注意,这里没有强制执行 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

}

但是有两个相关的问题。

问题 1 - 如果作为抽象引用动物,则隐式参数将仅查找采用抽象类型(动物)而不是底层具体类型的函数。我怀疑解决方案可能是使用 ClassTags,因为 Scala 似乎不使用运行时信息?我尝试实现了这一点,但最终迷失了方向(我对 Scala 还很陌生!)。

问题 2 - 如果我的动物可以住在不止一种类型的 House 中,则会出现类似的问题,即使指定了具体的返回类型,编译器也会发现 Fish 的 2 个隐含对象不明确。我有点困惑在这里做什么!

我可以使用手动样板来设计解决方案以在运行时匹配类型,但这不是很可扩展。

感谢您的任何想法!其余代码如下。

编辑 - 这些链接似乎证实了我的怀疑。使用了编译时多态性,因此无法知道运行时类型:

http://like-a-boss.net/2013/03/29/polymorphism-and-typeclasses-in-scala.html

https://softwareengineering.stackexchange.com/questions/258698/is-it-possible-to-have-ad-hoc-polymorphism-with-runtime-dispatch

所以,我想我现在的问题是,鉴于此,有没有办法修改我的示例以使用运行时调度?

动物:

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

Houses and Implicits:

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

【问题讨论】:

    标签: scala reflection polymorphism erasure implicit-parameters


    【解决方案1】:

    所以基本上你想要以下设计:

    • 在编译时,HouseBase 的具体类型是已知的。
    • 在编译时,Animal 的具体类型未知
    • Animal 提供的运行时动物数据创建特定类型HouseBase
    • 无法更改 Animal 实现,也不想更改 HouseBase 实现。

    理想的事情当然是在编译时提供Animals 的具体类型。由于似乎对此有所了解(您知道在编译时为动物变量创建哪个HouseBase),您可以尝试使用shapeless 中的type-safe cast 来获得具体Option 的@987654332 @类型。

    但如果不可能,您必须使用动物的运行时调度。

    在这种情况下,我认为方法create 应该具有以下签名:

    def create[HB <: HouseBase](animal: Animal): Option[HB]
    

    您知道HouseBase 的具体类型,因此您不妨将其作为类型参数传递,返回值为Option,以说明提供的动物类型与适合的动物类型可能不匹配一个具体的HouseBase

    实现这一点的一种可能方法是使用单个对象的以下代码,该对象具有从Animals 生成HouseBases 的所有知识(也应该可以通过移动创建来实现相同的目的代码到具体的伴随对象HouseBases):

    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 中的样板代码可以通过使用PartialFunctions 来减少:

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

    【讨论】:

      【解决方案2】:

      您希望编译器在子类被静态声明为其超类的实例时推断其运行时类型。事实证明这是不可能的,所以除非您希望获得某种计算机科学奖,否则不要试图让它发挥作用!

      除了参数化您的HouseCreator 类,您可以将其编写为具有一个接受Animal 类型对象的create() 方法。它可以使用基于Animal 的运行时子类型匹配的大小写匹配来创建适当的House

      sealed trait HouseCreator {
          def create(animal: Animal): HouseBase {
              animal match {
                  case dog: Dog => new Kennel(Seq(dog.boneName))
                  case fish: Fish => // etc...
              }
          }
      } 
      

      这只能返回一个HouseBase 对象而不是一个特定的子类(至少我在这里实现了它)。您也可以始终大小写匹配返回值。

      【讨论】:

        猜你喜欢
        • 2012-12-27
        • 1970-01-01
        • 1970-01-01
        • 2011-08-29
        • 2016-03-12
        • 2018-08-01
        • 1970-01-01
        • 2012-08-25
        • 2020-11-24
        相关资源
        最近更新 更多