Question

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.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top