scalatype-parameterpartially-applied-type

Difficulty writing method with partially applied type parameter and typetag


I am looking for a way to remove type parameter S from the call to apply in the following example:

object Attribute {
  trait Int    [S] extends Attribute[S]
  trait Boolean[S] extends Attribute[S]  // etc.
}
sealed trait Attribute[S]

trait Attributes[S] {
  protected def get(key: String): Option[Attribute[S]]

  def apply[A <: Attribute[S]](key: String)
                              (implicit tag: reflect.ClassTag[A]): Option[A] = 
    get(key) match {
      case Some(attr: A) => Some(attr)
      case _             => None
    }
}

With the above definition, a test case would be:

trait Test[S] {
  def map: Attributes[S]

  map[Attribute.Int[S]]("foo")
}

What I'm trying to do, is modify the apply definition to allow the following:

trait Test2[S] {
  def map: Attributes[S]

  map[Attribute.Int]("foo")   // use partially applied attribute type
}

EDIT: So following up on the suggestion of Marius and the comments, why is the following still producing an erasure warning:

import reflect.runtime.universe._

trait Attributes[S] {
  protected def get(key: String): Option[Attribute[S]]

  def apply[A[x] <: Attribute[x]](key: String)
                                 (implicit tag: TypeTag[A[S]]): Option[A[S]] =
    get(key) match {
      case Some(attr: A[S]) => Some(attr)
      case _                => None
    }
}

To me that clearly doesn't make sense. On the one hand I have the full type tag for A[S] available. On the other hand, it should even work completely in its absence, as Attribute is invariant in S, so if I get an Option[Attribute[S]], and I match against Some(attr: A[x]), the only possibility is that x == S.


EDIT 2: The condition for the solution is that the shape of Attribute trait is not changed, e.g. not moving type constructor parameter S to a member field.


Solution

  • Have you considered leveraging the unapply of the implicit ClassTag? If I understand the docs correctly, the unapply from the tag will return a None if attr does not fully match its type. If it does match, it will return a Some of type A[S]. So refactored to use unapply, your code would look like this:

    def apply[A[_] <: Attribute[_]](key: String)
      (implicit tag: reflect.ClassTag[A[S]]): Option[A[S]] =
      get(key) match {
        case Some(attr) => tag.unapply(attr)
        case _             => None
      }