I have a basic example from chapter 20.7 of Programming in Scala (Martin Odersky, Lex Spoon and Bill Venners), on the topic of Abstract Types. The code below is from Listing 20.10, except that I added the two last lines which seem ostensibly implied by the previous example:
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
As I stated above, the two lines where bossy is declared as an Animal are not actually in the example, but seem a very reasonable conceptual leap. At the level of the abstract class Animal (the declared type of bossy), the type member SuitableFood is still abstract. So, nothing will satisfy the compiler, even though it looks as if it wants a path-dependent-type at the method call.
If I declare my val to be of type Cow, the method call works, as follows:
val bessy: Cow = new Cow
bessy.eat(new Grass) // happy-happy
Given that there is nothing I could put in the 'eat()' method call for bossy (declared as an Animal) to satisfy the compiler, why does the compiler even allow bossy to be declared as an Animal/instantiated as a Cow? In other words, what possible use allowing the object declaration/instantiation, but not the method call, have?
Are there "best practices" for this feature in Scala, given that abstract member type refining seems to deliberately allow something normally forbidden in OO programming? Perhaps someone has found a killer-use?
I very much desire to see this behavior as something that makes perfect sense. What is the motivating use-case for this feature, i.e., declaring an abstract type, then refining that type in a derived class such that the subtype has a more refined type than the supertype?