Reputation: 793
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
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 ofA
, thenBSONReader[B]
is a subclass ofBSONReader[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 ofA
, thenBSONWriter[B]
is a superclass ofBSONWriter[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 MyObj
s 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