1

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.

schlicht
  • 4,735
  • 1
  • 13
  • 23

1 Answers1

1

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.

Community
  • 1
  • 1
mpilquist
  • 3,855
  • 21
  • 22
  • Thanks for your answer! I see now, that I stated the question to simple. My problem lies also with how I use this transformation. I tried basically the same as your first solution and got typeTag[A].tpe = Any with my code. I will update my question shortly. I don't now how to use your second solution probably(No TypeTag available for Int) – schlicht Mar 17 '13 at 08:48
  • Ah, I see. The reason Any is getting inferred is that the compiler doesn't have any information about List or Map's type params in the body of the map function. – mpilquist Mar 17 '13 at 13:23
  • Consider the partial function: `val pf: PartialFunction[List[Int], List[Int]] = { case l => l.reverse }`. What should `map` do with this example? It looks like the map function is assuming that the `~>` passed to it is defined for a type that does not take params. I can get `map` to work under that assumption but I wonder if the problem should be tackled differently? Maybe restate what you are trying to do? – mpilquist Mar 17 '13 at 14:48
  • I'm trying do write a generic map function for an AST. Example: Transform one AST to another where all identifiers are renamed. A little bit like term rewriting in Kiama, which I unfortunately can't use. – schlicht Mar 18 '13 at 10:46
  • Thanks to your hint with List or Map's type params I got it working, because I know at every point what kind of Map or List I'm using map on :). Also I added map[A: TypeTag](...), and test if f is defined on x instead of y. Thank you! – schlicht Mar 18 '13 at 11:43