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.