Search code examples
scalagenericstypesabstract-type

Abstract types in method parameters


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?


Solution

  • 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.