2017-02-20 27 views
1

我在Scala编程的第20.7章(Martin Odersky,Lex Spoon和Bill Venners)中有关于抽象类型主题的基本示例。下面的代码是从清单20.10,除了我添加了似乎表面上通过在前面的例子中隐含的最后两行:为什么要避免被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) {} 
} 
class Fish extends Food 
val bossy: Animal = new Cow // If the compiler were helpful, this would error. 
bossy.eat(new Grass) // error! 
// type mismatch; found: Grass, required: bossy.SuitableFood 

正如我如上所述,其中颐指气使被声明为动物的两条线是不实际在这个例子中,但似乎是一个非常合理的概念上的飞跃。在抽象类Animal(被称为bossy的类型)的层次上,类型成员SuitableFood仍然是抽象的。因此,即使看起来好像在方法调用时需要路径依赖类型,编译器也不会满足要求。

如果我宣布我的VAL为类型奶牛,方法调用的作品,如下:

val bessy: Cow = new Cow 
bessy.eat(new Grass) // happy-happy 

鉴于有什么我可以把在'吃()方法调用的霸道(声明为动物)以满足编译器,为什么编译器甚至允许bossy被声明为Animal /实例化为Cow?换句话说,允许对象声明/实例化,但不包括方法调用,有什么可能的用途?

鉴于抽象成员类型精炼似乎故意允许在面向对象编程中被禁止的东西,在Scala中是否存在此功能的“最佳实践”?也许有人找到了杀手锏?

我非常希望看到这种行为是非常有意义的。这个特性的动机用例是什么,即声明一个抽象类型,然后在派生类中细化该类型,使得该子类型具有比超类型更精确的类型?

+0

好吧。这不是编译错误。你仍然可以在你的bossy val上匹配模式,以便能够获得正确的子类型并在其上调用正确的方法。 – Falmarri

+1

这已被问到[这里](http://stackoverflow.com/questions/20070998/abstract-type-in​​-scala),[here](http:// stackoverflow。com/questions/20754143/no-dynamic-binding-when-abstract-type-in​​volved-in-scala),[here](http://stackoverflow.com/questions/32161100/scala-types-class-a-is不等于这里t是is型ta),和[这里](http://stackoverflow.com/questions/37756383/path-dependent-types-example-doesnt-work )。 – jwvh

回答

1

鉴于没有任何东西可以放在为了满足编译器而在'eat()'方法中调用bossy(声明为动物),为什么编译器甚至允许bossy被声明为Animal /实例化为牛?

  1. 有:bossy.eat((new Grass).asInstanceOf[bossy.SuitableFood])。当然,这并不意味着你应该像这样写代码。

  2. 即使有没有,有很多的事情可以做bossy没有调用eat方法:把它变成一个List,得到其哈希码,等等,等等

0

你仍然可以做其他有用的事情。如果你能证明它是SuitableFood,你可以制作一个AnimalFood。 当你可以让一个Animal呕吐时,你知道他抛出的所有东西都是他可以吃的东西,因为他以前吃过。即使您不确定它是否是GrassFish,也知道它是Food。所以你可以对它进行操作,对于每种类型的Food都是可能的。

scala> :paste 
// Entering paste mode (ctrl-D to finish) 

abstract class Food { def name: String } 
class Grass extends Food { def name = "Grass" } 
abstract class Animal { 
    type SuitableFood <: Food 
    def eat(food: SuitableFood): Unit 
    def throwUp: Option[SuitableFood] 
} 

class Cow extends Animal { 
    type SuitableFood = Grass 
    private[this] var digesting: List[Grass] = Nil 
    def eat(food: Grass) { 
    digesting = food :: digesting 
    } 
    def throwUp = digesting match { 
    case Nil => None 
    case food :: rest => 
     digesting = rest 
     Some(food) 
    } 
} 

def dispose(food: Food) = println(s"Disposing of some ${food.name}.") 

// Exiting paste mode, now interpreting. 


scala> val animal: Animal = { val cow = new Cow; cow.eat(new Grass); cow } 
animal: Animal = [email protected] 

scala> animal.throwUp foreach animal.eat // can eat his own vomit :s 

scala> animal.throwUp foreach dispose // I can dispose of all food 
Disposing of some Grass.