dimanche 3 mai 2015

Getting over type parameter bounds error with type checked by TypeTag

I think I am close to start understanding type erasure and the Scala reflection API. But I'm still in a world of pain...

How do I deal with this problem? I have a class that can take generic functions as arguments, and I need to do different things depending on the type from the function output. I also need to be able to "extract" the types. (This is all related to another question of mine, which helped me a lot, but still didn't solve my real problem.)

I know I can do the following:

import scala.reflect.runtime.universe._

object ReflectiveMysteryA extends App {

  def stupidFunc[A: TypeTag](arg: List[A]) = typeOf[A] match {
    case t if t =:= typeOf[List[String]] =>
      println("its a list of list of string")
    case t if t =:= typeOf[Set[Int]] =>
      println("its a list of set of int")
    case t if t =:= typeOf[Set[Long]] =>
      println("its a list of set of Long")
    case _ =>
      println("WTF")
  }

  stupidFunc(List(List("a", "bc"), List("b", "def")))
  stupidFunc(List(Set(1, 2), Set(2, 3, 4)))
  stupidFunc(List(Set(0L, 432L), Set(321L)))
  stupidFunc(List(Set("a", "bc", "def")))
  // stupidFunc(Set(Set("a", "bc", "def")))  // Doesn't compile, "type mismatch"
}
// its a list of list of string
// its a list of set of int
// its a list of set of Long
// WTF

This is all good good, we test the type using TypeTag and then we do the corresponding stuff. But it only works as long as you don't really need to be sure about that type you tested...

For instance, now I have a function like this, with type parameter bounds and an implicit argument that is used to "extract" type B from type LSB from the function argument:

object ReflectiveMysteryB extends App {

  def specialFunc[LSB <: List[Set[_]], B: TypeTag](vv: LSB)(implicit ev: List[Set[B]] =:= LSB) = {
    typeOf[B] match {
      case t if t =:= typeOf[Int] =>
        println("list of set of int")
      case t if t =:= typeOf[Long] =>
        println("list of set of long")
    }
  }

  specialFunc(List(Set(1, 2), Set(2, 3, 4)))
  specialFunc(List(Set(0L, 432L), Set(321L)))
  // specialFunc(List(List("a", "bc"), List("b", "def"))) // Doesn't compile, "type parameter bounds"
}
// list of set of int
// list of set of long

This is working fine too. The problem happens when I try to compose the two things:

object ReflectiveMysteryC extends App {

  def stupiderFunc[A: TypeTag](arg: List[A]) = typeOf[A] match {
    case t if t =:= typeOf[List[String]] =>
      println("its a list of list of string")
    case t if t =:= typeOf[Set[Int]] =>
      specialFunc(arg)
    case t if t =:= typeOf[Set[Long]] =>
      specialFunc(arg)
    case _ =>
      println("WTF")
  }

  def specialFunc[LSB <: List[Set[_]], B: TypeTag](vv: LSB)(implicit ev: List[Set[B]] =:= LSB) = {
    typeOf[B] match {
      case t if t =:= typeOf[Int] =>
        println("list of set of int")
      case t if t =:= typeOf[Long] =>
        println("list of set of long")
    }
  }

  stupiderFunc(List(List("a", "bc"), List("b", "def")))
  stupiderFunc(List(Set(1, 2), Set(2, 3, 4)))
  stupiderFunc(List(Set(0L, 432L), Set(321L)))
  stupiderFunc(List(Set("a", "bc", "def")))
}

That won't compile

Error:(45, 7) inferred type arguments [List[A],Nothing] do not conform to method specialFunc's type parameter bounds [LSB <: List[Set[_]],B]
      specialFunc(arg)
      ^
Error:(45, 19) type mismatch;
 found   : List[A]
 required: LSB
      specialFunc(arg)
                  ^
Error:(45, 18) Cannot prove that List[Set[B]] =:= LSB.
      specialFunc(arg)
                 ^
Error:(47, 7) inferred type arguments [List[A],Nothing] do not conform to method specialFunc's type parameter bounds [LSB <: List[Set[_]],B]
      specialFunc(arg)
      ^
Error:(47, 19) type mismatch;
 found   : List[A]
 required: LSB
      specialFunc(arg)
                  ^
Error:(47, 18) Cannot prove that List[Set[B]] =:= LSB.
      specialFunc(arg)
                 ^

To make it work, we could do the following:

object ReflectiveMysteryD extends App {

  def stupiderFunc[A: TypeTag](arg: List[A]) = typeOf[A] match {
    case t if t =:= typeOf[List[String]] =>
      println("its a list of list of string")
    case t if t =:= typeOf[Set[Int]] =>
      specialFunc(arg.asInstanceOf[List[Set[Int]]])
    case t if t =:= typeOf[Set[Long]] =>
      specialFunc(arg.asInstanceOf[List[Set[Long]]])
    case _ =>
      println("WTF")
  }

  def specialFunc[B: TypeTag](vv: List[Set[B]]) = {
    typeOf[B] match {
      case t if t =:= typeOf[Int] =>
        println("list of set of int")
      case t if t =:= typeOf[Long] =>
        println("list of set of long")
    }
  }

  stupiderFunc(List(List("a", "bc"), List("b", "def")))
  stupiderFunc(List(Set(1, 2), Set(2, 3, 4)))
  stupiderFunc(List(Set(0L, 432L), Set(321L)))
  stupiderFunc(List(Set("a", "bc", "def")))
}
// its a list of list of string
// list of set of int
// list of set of long
// WTF

But now my problem is: How can I match the general case of List[Set[_]] in stupiderFunc in order to apply specialFunc to arg without having to list all the specific Int or Long types? Is there a way to use asInstanceOf without fixing the inner type parameter? Or is there a way to fix the implicit approach so it is able to figure out that args actually conforms to the expected type by the time we are calling specialFunc inside the match?

Is this a problem I might be using macros to solve? How should I proceed in that case?





Aucun commentaire:

Enregistrer un commentaire