Question

I have the following trait (to get kind of rank 2 polymorphism click)

type Id[A] = A
trait ~>[F[_], G[_]] {
def apply[A](a: F[A]): G[A]
def isDefinedAt[A](a: A): Boolean}

And a function to transform a partial function to this trait:

implicit def pft[B: ClassTag](f: PartialFunction[B, B])= new (Id ~> Id) {
def apply[A](a: A): A = f(a.asInstanceOf[B]).asInstanceOf[A]
def isDefinedAt[A: ClassTag](a: A)(implicit ev2: ClassTag[A]) : Boolean = /*type check*/
               f.isDefinedAt(a.asInstanceOf[B]) }

So my problem is the isDefinedAt method. I have to check if A is an instance of B at runtime. a.isInstanceOf[B] doesn't work due to type erasure.

I tried to use TypeTag/ClassTag and for B that works just fine, but the Type for A ist always Any.

So, how can I check if A is an instance of B?

Update: I use it in this code:

  def map[A](f: Id ~> Id, x: A): A =
    {
      val y = x match {
        //many matches more on my own data structures
        case l : List[_] => l.map(map(f,_))
        case m : Map[_,_] => m.map(map(f,_))
        case (a,b) => (map(f,a),map(f,b))
        case a => a
      }
      if (f.isDefinedAt(y))
        f(y).asInstanceOf[A]
      else
        y.asInstanceOf[A]
    }

If I use the ~> directly everything works fine with typeTag[A].tpe <:< typeTag[B].tpe.

But if I use it with this map function, typeTag[A].tpe is always Any.

Was it helpful?

Solution

If you are okay with modifying the signature of isDefinedAt to take a TypeTag, you can implement pft this way:

  type Id[A] = A
  trait ~>[F[_], G[_]] {
    def apply[A](a: F[A]): G[A]
    def isDefinedAt[A: TypeTag](a: A): Boolean
  }

  implicit def pft[B: TypeTag](f: PartialFunction[B, B]) = new (Id ~> Id) {
    def apply[A](a: A): A = f(a.asInstanceOf[B]).asInstanceOf[A]
    def isDefinedAt[A: TypeTag](a: A): Boolean =
      typeTag[A].tpe =:= typeTag[B].tpe && f.isDefinedAt(a.asInstanceOf[B])
  }

This solution gets a TypeTag for both B and A and verifies that they are the same type before delegating to the partial function's isDefinedAt method. For more information on type tags, see this answer.

For example:

val halfEven: PartialFunction[Int, Int] = {
  case n if n % 2 == 0 => n / 2
}

val halfEvenT: Id ~> Id = halfEven

halfEvenT.isDefinedAt(1) // false
halfEvenT.isDefinedAt(2) // true
halfEvenT.isDefinedAt("test") // false

More generically, you could define a constrained partial transformation as taking an additional higher kinded type that constrains A:

  trait ConstrainedPartialTransformation[F[_], G[_], C[_]] {
    def apply[A: C](a: F[A]): G[A]
    def isDefinedAt[A: C](a: A): Boolean
  }

  implicit def cpft[B: TypeTag](f: PartialFunction[B, B]) = new ConstrainedPartialTransformation[Id, Id, TypeTag] {
    def apply[A: TypeTag](a: A) = f(a.asInstanceOf[B]).asInstanceOf[A]
    def isDefinedAt[A: TypeTag](a: A) =
      typeTag[A].tpe =:= typeTag[B].tpe && f.isDefinedAt(a.asInstanceOf[B])
  }

This is similar to how Scalaz 7 supports natural transformations where there is a context bound on A. See ConstrainedNaturalTransformation from Scalaz 7.

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