scalatypesabstract-type

How to instruct Scala not to upcast abstract type?


Considering Bulldog:

trait Animal {
  type Food

  def defaultFood(): Food
}
class Bulldog extends Animal {
  type Food = Steak

  ... implementations ...
}

The function Bulldog.defaultFood() works just fine for compiler (though my syntax highlighter gave an error, this is no big deal):

val bulldog = new Bulldog()
val df: bulldog.Food = bulldog.defaultFood()

However if bulldog is enclosed inside another class, all hell break loose:

class Kennel(val animal: Animal) {
}
def getSupply(kennel: Kennel): kennel.animal.Food = {
  ... implementation ...
}

val kennel = new Kennel(bulldog)
val df2: bulldog.Food = getSupply(kennel)

Scala compiler will throw a compile error:

type mismatch;
 found   : Option[kennel.animal.V] where val kennel: Kennel
 required: bulldog.Food

Is this feature currently missing in Scala? Is there any way to make it work?


Solution

  • Your code has a compilation problem - kennel.animal is unresolvable since class Kennel doesn't expose animal as a public field; this is easily resolved by adding val in front of animal.

    What seems to be bothering you though is that Kennel has no knowledge about the underlying animal, other than it's an Animal. Passing a bulldog is seen as passing some animal.

    Try adding some more type info to Kennel:

    class Kennel[A <: Animal](val animal: A) {  }
    
    def getSupply[A <: Animal](kennel: Kennel[A]): kennel.animal.Food
    
    val kennel = new Kennel(bulldog)
    val df2: bulldog.Food = getSupply(kennel)
    

    Now Kennel knows the exact type of animal (e.g. Bulldog) and getSupply is able to return the exact type of food for that animal.

    Here's full working code that you can try out:

    trait Steak {
      override def toString() = "steak"
    }
    
    trait Animal {
      type Food
      def defaultFood(): Food
    }
    
    class Bulldog extends Animal {
      type Food = Steak
      def defaultFood() = new Steak {}
    }
    
    class Kennel[A <: Animal](val animal: A)
    
    object Test extends App {
    
      def getSupply[A <: Animal](kennel: Kennel[A]): kennel.animal.Food = kennel.animal.defaultFood()
    
      val bulldog = new Bulldog()
      val kennel = new Kennel(bulldog)
      val df2: bulldog.Food = getSupply(kennel)
    
      println(df2) // prints "steak"
    }