2
votes

Say you have a method definition like this in Scala:

def handle[T](fn: T => Unit): Unit

Is it possible to pattern match on the type of the function parameter T to call up a different method depending on the type of T?

Would you need to rewrite it instead to take a Function1 instead and then pattern match on it?

I've tried the following but does not work due to type erasure:

class A {
  def x(fn: A => Unit): Unit = fn(this)
}

class B {
  def y(fn: B => Unit): Unit = fn(this)
}

def handle[T](fn: Function1[T, Unit]): Unit = {
  fn match {
    case fnA: Function1[A, Unit] =>
      new A().x(fnA)
    case fnB: Function1[B, Unit] =>
      new B().y(fnB)
  }
}

Maybe with abstract types?

2
Is it OK to change the signature of handle? And to change A and B? - Beryllium

2 Answers

1
votes

You can do this with a typeclass, then no reflection or type tagging is required:

  // Typeclass defines things that can be handled
  trait Handleable[T] {
    def handle(fn: T => Unit)
  }

  // Two typeclass instances, one for A and one for B
  implicit object AHandleable extends Handleable[A] {
    def handle(fn: A => Unit) = new A().x(fn)
  }

  implicit object BHandleable extends Handleable[B] {
    def handle(fn: B => Unit) = new B().y(fn)
  }

  // implicitly grab the instance for whichever type we are using (A, B...)
  def handle[T](f: T => Unit)(implicit h: Handleable[T]) = h.handle(f)
  //or equivalently:
  //def handle[T: Handleable](f: T => Unit) = implicitly[Handleable[T]].handle(f)

  handle((a: A) => println(a))                    //> A
  handle((b: B) => println(b))                    //> B

Since A and B have no useful common superclass (I am assuming you can't simply give them one!) the typeclass pattern allows us to "bolt on" a common supertrait without modifying the original classes ("ad-hoc polymorphism").

We can later support additional cases (for C, D, etc) without modifying the handle() method, by adding further typeclass instances.

0
votes

I think I found an answer using TypeTag instances:

import scala.reflect.runtime.universe._

class A {
  def x(fn: A => Unit): Unit = fn(this)
  override def toString = "A"
}
class B {
  def y(fn: B => Unit): Unit = fn(this)
  override def toString = "B"
}

def handle[T : TypeTag](fn: Function1[T, Unit]): Unit = typeOf[T] match {
  case t if t =:= typeOf[A] => new A().x(fn.asInstanceOf[Function1[A, Unit]])
  case t if t =:= typeOf[B] => new B().y(fn.asInstanceOf[Function1[B, Unit]])
}

handle[A] { a: A =>
  println("It's " + a)
}
handle[B] { b: B =>
  println("It's " + b)
}

That prints the expected output:

It's A
It's B

If anyone has a better solution, let me know :)