I have case classes and for each case class T I define an Entity[T] implicit. These instances define an Id type that is specific to each T. I then define an abstract Table[T] class with a retrieve method that takes an identifier of the Id type defined in T through a type projection...
But it does not work, I get type mismatches:
scalac generic-type-projection.scala
generic-type-projection.scala:31: error: type mismatch;
found : id.type (with underlying type Entity[T]#Id)
required: _5.Id where val _5: Entity[T]
val key = implicitly[Entity[T]].keyFromId(id) // Type mismatch on 'id'.
^
generic-type-projection.scala:41: error: type mismatch;
found : String("dummy")
required: Entity[A]#Id
t.retrieve("dummy")
^
two errors found
Here's the code:
import java.util.UUID
trait Entity[T] {
type Id
type Key
def getId(entity: T): Id
def keyFromId(id: Id): Key
}
case class A(f1: String, f2: Int)
object AImplicits {
implicit object AEntity extends Entity[A] {
type Id = String
type Key = UUID
def getId(entity: A) = entity.f1
def keyFromId(id: String) = UUID.fromString(id)
}
}
object Main {
abstract class Table[T: Entity] {
def store(entity: T): Unit
def retrieve(id: Entity[T]#Id): Option[T]
}
def makeTable[T: Entity]: Table[T] =
new Table[T] {
def store(entity: T): Unit = {}
def retrieve(id: Entity[T]#Id): Option[T] = {
val key = implicitly[Entity[T]].keyFromId(id) // Type mismatch on 'id'.
None
}
}
def main(args: Array[String]) {
import AImplicits._
val t = makeTable[A]
val a = A("dummy", 9)
t.store(a)
t.retrieve("dummy") // Type mismatch on "dummy".
}
}
Any help would be very much appreciated.
The type mismatch is a bit easier to see when you expand the context bound shorthand. Remember, the [T : Entity]
syntax is just syntactic sugar for requiring an implicit Entity[T]
to be in scope. So the header of your makeTable
function is actually:
def makeTable[T](implicit entity: Entity[T]): Table[T] =
Now with that implicit parameter in scope, your implicitly
call in the function will grab that value (actually, the new Table[T]
constructor grabs the implicit, and then implicitly
grabs it from there), so the makeTable
function in its expanded form:
def makeTable[T](implicit entity: Entity[T]): Table[T] =
new Table[T] {
def store(entity: T): Unit = {}
def retrieve(id: Entity[T]#Id): Option[T] = {
val key = entity.keyFromId(id) // Type mismatch on 'id'.
None
}
}
Notice that there is no conversion taking place for the type declaration of the id
parameter of retrieve
. Essentially, the compiler is complaining that entity.keyFromId
is expecting a parameter of type entity.Id
, but your id
parameter is of the abstract type Entity[T]#Id
.
The problem here is that you want to hide the Entity
and the ids from users of the Table
class, yet you want to expose the type of the Id
in one of the methods. Suppose I have two instances of Table[T]
, but they use different Entity[T]
implementations -- so different that their Id
types are actually different. How would the users of the table instances know which type to pass to retrieve
functions? You could fix this by adding another type parameter to the Table
class. You can still abstract away the id/key generation logic, but this will allow the compiler to type check to make sure the retrieve
function is getting the right type of parameter.