Ehud Kaldor
Ehud Kaldor

Reputation: 793

getting "incompatibe type" in returning an object instace

I'm writing a Play! 2.1 application using ReactiveMongo. each persistable case class has an object that holds 2 implicit objects, implementing BSONReader[...] and BSONWriter[...], and each case class has methods to return these:

  trait Persistable {
    implicit def getReader: BSONReader[Persistable]
    implicit def getWriter: BSONWriter[Persistable]
    val collectionName: String
  }

  case class MyObj () extends Persistable {
    override val collectionName: String = MyObj.collectionName
    override def getReader: BSONReader[MyObj] = MyObj.MyObjBSONReader
    override def getWriter: BSONWriter[MyObj] = MyObj.MyObjBSONWriter
  }

  object MyObj{
    val collectionName: String = "MyObj"

    implicit object MyObjBSONReader extends BSONReader[MyObj] {
      def fromBSON(document: BSONDocument): MyObj = {
        val doc = document.toTraversable
        new MyObj(
        )
      }
    }

    implicit object MyObjBSONWriter extends BSONWriter[MyObj] {
      def toBSON(myObj: MyObj) = {
        BSONDocument(
        )
      }
    }

for some reason, getReader seems to work fine, but getWriter errors:

overriding method getWriter in trait Persistable of type = reactivemongo.bson.handlers.BSONWriter[models.persistable.Persistable]; method getWriter has incompatible type

what am i doing wrong? both seem to have similar signatures. another hint is that if i remove the return type from getWriter, i get complie time error in eclipse:

type mismatch; found : models.persistable.MyObj.MyObjBSONWriter.type required: reactivemongo.bson.handlers.BSONWriter[models.persistable.Persistable]

UPDATE:

I did as @AndrzejDoyle said below, but then the implementation of Persister, which was the heart of this exercise, complains:

def insert(persistable: Persistable) = {
    val collection = db(persistable.collectionName)
    import play.api.libs.concurrent.Execution.Implicits._

    implicit val reader = persistable.getReader
    implicit val writer = persistable.getWriter
    collection.insert(persistable)
}

error:

trait Persistable takes type parameters

Upvotes: 0

Views: 182

Answers (1)

Andrzej Doyle
Andrzej Doyle

Reputation: 103817

It is due to covariance and contravariance.

The mongodb reader is defined as BSONReader[+DocumentType]. The + in the generic parameter, means that this class is covariant in that parameter. Or more fully,

If B is a subclass of A, then BSONReader[B] is a subclass of BSONReader[A].

Therefore you can use a BSONReader[MyObj] everywhere that a BSONReader[Persistable] is required.

On the other hand, the writer is contravariant: BSONWriter[-DocumentType]. This means that

If B is a subclass of A, then BSONWriter[B] is a superclass of BSONWriter[A].

Therefore your BSONWriter[MyObj] is not a subclass of BSONWriter[Persistable], and so cannot be used in its place.


This might seem confusing initially (i.e. "why does contravariance make sense when it's 'backwards'?"). However if you think about what the classes are doing, it becomes clearer. The reader probably produces some instance of its generic parameter. A caller then might expect it to produce a Persistable - if you have a version that specifically produces MyObjs instead then this is fine.

The writer on the other hand, is probably given an object of its generic parameter. A caller with a BSONWriter[Persistable] will call the write() method, passing in an instance of Persistable to be written. Your implementation can only write instances of MyObj, and so it doesn't actually match the interface. On the other hand, a BSONWriter[Object] would be a subclass of any BSONWriter, since it can (from a type perspective) accept any type as an argument.


The fundamental problem seems to be that your Persistable trait is looser than you intended. You probably want each implementation to return a reader and writer parameterized on itself, rather than on Persistable in general. You can achieve this via self-bounded generics:

trait Persistable[T <: Persistable[T]] {
    implicit def getReader: BSONReader[T]
    implicit def getWriter: BSONWriter[T]
    val collectionName: String
}

and then declare the class as MyObj[MyObj]. Now the reader and writer are expected to be parameterised on MyObj, and your existing implementations will compile.

Upvotes: 1

Related Questions