Question

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.

Was it helpful?

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
  }

OTHER TIPS

I think you want higher-kinded types. I rewrote your code like so:

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

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

Please note that now the A type parameter is a higher order type. As such, at each occurrence within the apply method you will need to "apply" A to another type in order to obtain a proper type.

This question provides more insight for higher-order types:

What is a higher kinded type in Scala?

One way. Note that it takes a certain chutzpah to call any type an Int, even a nested one.

import scala.reflect._

sealed trait Attribute {
  type S
}

object Attribute {
  trait Ints extends Attribute { type S = Int }
}

/** A container for attributes of a certain type.
 */
trait Attributes[E] {
  type Attr = Attribute { type S <: E }
  protected def get(key: String): Option[Attr]

  /** Retrieve the value for the given key.
   *  @tparam A must be our notion of Attribute
   *  @return optionally the A you asked for
   */
  def apply[A <: Attr](key: String)(implicit tag: ClassTag[A]): Option[A] =
    get(key) match {
      case Some(attr: A) => Some(attr)
      case _             => None
    }
}
trait Test2 {
  /** Map keys to ints. */
  def map: Attributes[Int]

  /** Retrieve a value. */
  map[Attribute.Ints]("foo")   // use partially applied attribute type
}

This is just to show that @cmbaxter 's answer works as expected. I'll restate the initial code as well, so that it is fully executable:

// Attribute Interface

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

// Attribute Map Interface

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

  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
    }  
  }
}

// Test Case

class Test {
  val map = new Attributes[Foo] {
    def get(key: String) = key match {
      case "foo" => Some(new Attribute.Int    [Foo] { override def toString = "I" })
      case "bar" => Some(new Attribute.Boolean[Foo] { override def toString = "B" })
      case _     => None
    }
  }

  println(s"An int attr named `foo`: ${map[Attribute.Int    ]("foo")}")
  println(s"A bool attr named `foo`: ${map[Attribute.Boolean]("foo")}")
  println(s"An int attr named `bar`: ${map[Attribute.Int    ]("bar")}")
  println(s"A bool attr named `bar`: ${map[Attribute.Boolean]("bar")}")
  println(s"An int attr named `???`: ${map[Attribute.Int    ]("???")}")
  println(s"A bool attr named `???`: ${map[Attribute.Boolean]("???")}")
}

// Running

new Test

An int attr named `foo`: Some(I)
A bool attr named `foo`: None
An int attr named `bar`: None
A bool attr named `bar`: Some(B)
An int attr named `???`: None
A bool attr named `???`: None
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top