slouc slouc - 3 months ago 11
Scala Question

How to use type information from pattern matching?

I have the following setup:

trait TypeA { override def toString() = "A" }
trait TypeB { override def toString() = "B" }
trait TypeC { override def toString() = "C" }

def foo[T](t: T) = println(t)


Now I can do something like this:

val valueB: Any = new TypeB {}

val typedValue = valueB match {
case t: TypeA => foo(t)
case t: TypeB => foo(t)
case t: TypeC => foo(t)
}
// prints "B"


If I want to generalize this pattern matching block, I can simply do:

val typedValue = valueB match {
case t => foo(t)
}


and it will work. However, in my real use case I need to explicitly state the type information when invoking the method because there is no function argument to infer it from. So if
foo()
was a generic method parameterized with type parameter
T
, but without actual parameters of that type to infer from, can I generalize this into a pattern matching with just one case statement
(probably using the Reflection API)?

So, how to generalize this?

val typedValue = valueB match {
case t: TypeA => foo[TypeA]()
case t: TypeB => foo[TypeB]()
case t: TypeC => foo[TypeC]()
...
}

Answer

If I want to generalize this pattern matching block, I can simply do:

val typedValue = valueB match {
  case t => foo(t)
}

In general you can't. E.g. if foo(x: TypeA), foo(x: TypeB) and foo(x: TypeC) are separate overloads. And that's the situation for your real code: you'd have to write separate methods for JsObject, etc. because those value calls just happen to have the same name; you can't write foo(x: JsValue) or foo[T <: JsValue](x: T) which would do what you want (without the same match which you want to avoid).

In the case where you do have a single polymorphic method: because generic arguments get erased, if you have def foo[T]() = ..., foo[TypeA](), foo[TypeB]() and foo[TypeC]() will execute the same actual code (this doesn't apply to classOf, isInstanceOf or asInstanceOf, but those are the only exceptions and it's because they aren't really generic methods). So you can just call foo[<type-of-valueB>]. For them to be different, foo has to have an implicit argument which depends on T, e.g.

trait Baz[A] { ... }
object Baz {
  implicit val bazTypeA: Baz[TypeA] = ...
  ...
}

def foo[A]()(implicit baz: Baz[A]) = ...

In this case the way to avoid branching is for the method calling foo to accept the same implicit:

def bar[A](value: A)(implicit baz: Baz[A]) = foo[A]()

bar(new TypeA) // uses bazTypeA
Comments