Reputation: 4775
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.
Upvotes: 1
Views: 385
Reputation: 3855
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.
Upvotes: 1