1
votes

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?

2
Well. It's not a compile error. You could still pattern match on your bossy val to be able to get its correct subtype and call the right method on it.Falmarri
This has been asked about here, here, here, and here.jwvh

2 Answers

1
votes

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?

  1. There is: bossy.eat((new Grass).asInstanceOf[bossy.SuitableFood]). Of course, this doesn't mean you should ever write code like this.

  2. Even if there weren't, there are a lot of things you can do with bossy without calling eat method: put it into a List, get its hash code, etc. etc.

0
votes

You can still do other useful stuff. You can make an Animal eat Food if you can prove that it is SuitableFood. When you can make an Animal throw up, you know that everything he throws up is something he can eat, because he's eaten is before. And you know it's Food even if you don't know for sure whether it's Grass or Fish. So you can do operations on it that are possible for every type of 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 = Cow@506dcf55

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.