如何解释以下关于路径依赖类型和多态的代码

时间:2017-09-15 09:32:41

标签: scala

我将以下代码作为scala脚本运行,然后获得了编译器 错误

class Food
abstract class Animal {
  type SuitableFood <: Food
  def eat(food: SuitableFood)
}

class Grass extends Food
class Cow extends Animal {
  type SuitableFood = Grass
  override def eat(food: Grass) {}
}

val bessy:Animal = new Cow
bessy eat (new Grass)

然后我得到了一个编译器错误

script.scala:14: error: type mismatch;
 found   : this.Grass
 required: this.bessy.SuitableFood
bessy eat (new Grass)
           ^

接下来,我更改了一些代码如下:

val bessy = new Cow
class Fish extends Food
bessy eat (new Fish)

我收到了另一个错误

script.scala:18: error: type mismatch;
 found   : this.Fish
 required: this.Grass
bessy eat (new Fish)
           ^

两种情况下所需的类型不同,如何解释?

2 个答案:

答案 0 :(得分:2)

我无法解释这些编译器错误的确切机制,但我确实相信这些问题需要使用类型参数化。 我可以用这种方式建模的最佳方式是:或多或少干净的方式:

object Amimals {

  def main(args:Array[String]):Unit = {

    trait Food
    trait Animal[AllowedFood<:Food]{
      def eat(food:AllowedFood)
    }

    class Grass extends Food
    class Meat extends Food

    class Herbivore[AllowedFood<:Grass] extends Animal[AllowedFood] {
      override def eat(food:AllowedFood) = {println("I can eat only grass")}
    }

    class Cow extends Herbivore[Grass]


    val bessy = new Cow
    bessy eat new Grass
    //bessy eat new Meat
  }
}

通过这种方式,您可以实例化Cow实例,并且它将知道它只能是GrassGrass衍生物。 如果您尝试取消注释最后一行,则会产生编译错误:

Error:(22, 15) type mismatch;
 found   : Meat
 required: Grass
    bessy eat new Meat
              ^

答案 1 :(得分:2)

<强> TL;当您向上转移到Animal时,你会删除}的知识编译器<{1}}

为了解释的目的,我冒昧地略微修改了bessy.SuitableFood = Grass的定义:

Cow

如果您专门使用class Cow extends Animal with Food { type SuitableFood = Grass def moo() = "moo" override def eat(food: SuitableFood) = println(moo()) } ,那么Cow是什么就没问题,因此编译器可以为您提供相当有用的错误消息。

SuitableFood

请注意,类型推断为您提供了最准确的信息。

当您向上转换为父类型(val bessy = new Cow // same as val bessy: Cow = new Cow bessy.eat(new Grass) // <- totally valid println(bessy.moo()) // <- and again bessy.eat(new Fish) // <- does not typecheck )时,编译器将丢失Animalbessy的知识,Cow的知识也是如此是bessy.SuitableFood。它只能告诉你它所知道的,所以错误信息是不同的

Grass

实际上,此时你根本无法调用val bessyAsAnimal: Animal = bessy //bessyAsAnimal.eat(new Grass) // <- won't typecheck, your problem (没有我们不考虑的类型转换)。让我们来看看保留这种能力的方法之一:细化类型:

bessyAsAnimal.eat(...)

这有点麻烦,但有效。我们不知道bessyRefinedAnimal是val bessyRefinedAnimal: Animal { type SuitableFood = Grass } = bessy bessyRefinedAnimal.eat(new Grass) // <- valid again //bessyRefinedAnimal.moo() // <- won't compile ,因此我们不能再调用Cow - 具体方法了。不过,您可以提供它,它只会接受Cow

因此,如果您只知道Grass,则无法提供它。试试这个:

Animal

那么,这一切有什么意义呢?那么,你仍然能够在不知道它喜欢哪种特定食物的情况下喂养动物,无论是def doSomethingWithAnimal(animal: Animal) = { // without casts, no call to animal.feed(...) will compile // tho if Animal has methods not dependent on SuitableFood, // those could be used } 还是Grass,但知道它是正确< / strong>一:

Fish

我们将能够从具体类型知识仍然存在的地方使用这种方法:

def feedAnimal(animal: Animal)(food: animal.SuitableFood) = {
  println("It's dinner time!")
  animal.eat(food)
  // animal.eat(new Grass) will not work
  // nor will animal.moo()
  // we can only use argument here
  println("Feeding routine complete")
}