Reputation: 2878
I was reading the section 20.7 of the book Programming in Scala and I was wondering why while this code compiles:
class Food
class Fish extends Food
class Grass extends Food
abstract class Animal {
type SuitableFood <: Food
def eat(food: SuitableFood)
}
class Cow extends Animal {
type SuitableFood = Grass
override def eat(food: Grass) {}
}
val bessy: Animal = new Cow
bessy eat (new bessy.SuitableFood)
This code does not (the rest of the code is the same as before, only the last line changes):
bessy eat (new Grass)
And as far as I understand the type of Grass is the same of Cow.SuitableFood.
Also, I have another question regarding this example:
If bessy is of type Animal, how can the compiler know that it needs a type SuitableFood -> Grass instead of a type Food? 'Cause trying to provide a new Food gives me a compile error of type mismatch, but the class Animal needs a type Food and the type of bessy is explicitly defined: Animal
Upvotes: 5
Views: 324
Reputation: 170899
I believe bessy eat (new bessy.SuitableFood)
compiling is a bug (which was fixed in 2.11). Because another subtype of Animal
could have a SuitableFood
for which new
makes no sense, e.g. type SuitableFood = Food
or even type SuitableFood = Food with Int
(Food with Int
is a perfectly nice subtype of Food
!).
Upvotes: 1
Reputation: 4231
Regarding the second part of your question: it doesn't. Animal
doesn't specify that its food is Food
, but some subtype of Food
. Would the compiler accept this, code like your example would compile, and wrongly so. The compiler doesn't know that the necessary subtype is Grass
(which is why eat(new Grass)
doesn't work either), it just knows that there are some foods your cow can't eat and is cautious about it.
Upvotes: 1
Reputation: 5030
It's because bessie
is declared Animal
rather than Cow
. bessie.SuitableFood
is a "path-dependent type" (see below).
Try this:
val clarabelle: Cow = new Cow
clarabelle eat (new Grass)
This works because the compiler can deduce that clarabelle.SuitableFood = Grass
from clarabelle
's declared type.
Since bessie
is declared Animal
, not Cow
, the compiler can't safely deduce that bessie.SuitableFood = Grass
.* When you say new bessie.SuitableFood
, the compiler generates code to look at the actual bessie
object and generate a new instance of the appropriate type. bessie.SuitableFood
is a "path-dependent type": the "path" (the bessie.
part) that leads to the last identifier (SuitableFood
) is actually part of the type. This enables you to have a custom version of a type for each individual object of the same class.
*Well, actually, I think that if the compiler were a little smarter, it could deduce that bessie.SuitableFood = Grass
, since bessie
is a val
, not a var
, and therefore won't change its type. In other words, the compiler ought to know that even though bessie
is declared Animal
, she's really a Cow
. Perhaps a future version of the compiler will make use of this knowledge, and perhaps there's a good reason why that wouldn't be a good idea, which someone more expert than I will tell us. (Postscript: One just did! See Travis Brown's comment below.)
Upvotes: 11