There's a simple example in Programming in Scala by Odersky et al on abstract types, but it does not seem to follow to it's logical conclusion [now edited to make this my exact code]:
class Food
class Grass extends Food
class FishFood extends Food
abstract class Animal {
type Feed <: Food
def eat(food: Feed)
}
class Cow extends Animal {
type Feed = Grass
override def eat(food: Grass) = {}
}
class Test extends App {
val cow: Animal = new Cow
cow.eat(new FishFood)
cow.eat(new Grass)
}
They explain this will prevent me doing (as above):
val cow: Animal = new Cow
cow.eat(new FishFood)
So far so good. But the next natural step does not seem to work either:
cow.eat(new Grass)
I get a compile error:
type mistmatch;
found : Grass
required: Test.this.cow.Feed
cow.eat(new Grass)
^
But cow.Feed is Grass, so why doesn't this work?
The problem here is that your val cow
is typed as Animal
rather than Cow
, so all that the compiler knows is that its eat
method expects some specific subtype of Food
, but it doesn't know which, and in particular it's unable to prove that that type is equal to Grass
.
You can see the difference this makes to the type of the method (as viewed from Animal
vs. as viewed from Cow
) by asking for it's eta-expansion,
scala> val cow: Animal = new Cow
cow: Animal = Cow@13c02dc4
scala> cow.eat _
res12: cow.Feed => Unit = <function1>
scala> cow.asInstanceOf[Cow].eat _
res13: Grass => Unit = <function1>
You'll notice that in the second, more precisely typed, case the compiler views the method as taking an argument of type Grass
rather than the abstract type cow.Feed
.