0

I'm new to Scala and I try to understand how generic abstract classes/methods are working

The code below does not compile, from the error message it seems that the parameters of the methods overriding the abstracts are not of the type give as parameter to the class

abstract class Pet(someString: String) {
    def name: String
}

class Cat(someString: String) extends Pet(someString) {
    val name = someString;
}

class Dog(someString: String) extends Pet(someString) {
    val name = someString;
}


abstract class Printer[+A] {
    def print[B >: A](printableObject: B): Unit
}

class CatPrinter extends Printer[Cat] {

    override def print[B >: Cat](cat: B): Unit = println("the cat name is: " + cat.name)
}
class DogPrinter extends Printer[Dog] {

    override def print[B >: Dog](dog: B): Unit = println("the dog name is: " + dog.name)
}


object Test {

    val myCat: Cat = new Cat("Booster")

    def printMyCat(printer: Printer[Pet]): Unit = {
        printer.print(myCat)
    }

    def main(args: Array[String]): Unit = {
        val catPrinter = new CatPrinter
        val dogPrinter = new DogPrinter

        printMyCat(catPrinter)
        printMyCat(dogPrinter)
    }
}

The compilation fails with the below message

ScalaFiddle.scala:20: error: value name is not a member of type parameter B&0
    override def print[B >: Cat](cat: B): Unit = println("the dog name is: " + cat.name)
                                                                                       ^
ScalaFiddle.scala:24: error: value name is not a member of type parameter B&0
    override def print[B >: Dog](dog: B): Unit = println("the dog name is: " + dog.name)

Any thoughts on why is the code not compiling and how I could make it work?

0

1 Answer 1

2

But when you write def print[B >: A](printableObject: B): Unit it means that method accepts A and all his parents (include Any). Any doesn't have a name property.

To fix this you should set upper type bound to Pet:

abstract class PetPrinter[+A <: Pet] {
    def print[B >: A <: Pet](printableObject: B): Unit
}
class CatPrinter extends Printer[Cat] {
    override def print[B >: Cat <: Pet](cat: B): Unit = println(s"the cat name is: ${cat.name}")
}
class DogPrinter extends Printer[Dog] {
    override def print[B >: Dog <: Pet](dog: B): Unit = println(s"the dog name is: ${dog.name}")
}

But I think better write something like this:

abstract class Pet(someString: String) {
  def name: String
}
class Cat(someString: String) extends Pet(someString) {
  override val name = someString
}
class Dog(someString: String) extends Pet(someString) {
    override val name = someString
}
class PetPrinter[-A <: Pet] {
    def print(printableObject: A): Unit = println(s"the pet name is: ${printableObject.name}")
}
object Test {
    def printPet[T <: Pet](pet: T, printer: PetPrinter[T]): Unit = {
        printer.print(pet)
    }

    def main(args: Array[String]): Unit = {
        val cat: Cat = new Cat("Cat")
        val dog: Dog = new Dog("Dog")

        val petPrinter = new PetPrinter[Pet]
        val catPrinter = new PetPrinter[Cat]

        printPet(cat, petPrinter)
        printPet(cat, catPrinter)
        printPet(dog, petPrinter)
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

There is no need for contravariance in your solution. It works fine even with class PetPrinter[A <: Pet]
You're right. Contravariance is needed for using PetPrinter[Pet] as PetPrinter[Cat], but in my solution printPet(cat, petPrinter) is printPet[Pet](cat: Pet, petPrinter: PetPrinter[Pet]) not printPet[Cat](cat: Cat, petPrinter: PetPrinter[Cat])

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.