ClassTag
The simplest case is when you can use a ClassTag
(the limitation for this case is given below). For that case you can simply add a context bound to the function type definition, and it just works:
import scala.reflect.ClassTag
protected final def validatePayload[T : ClassTag](payload: Option[Payload]) = {
// everything else is the same...
}
// Throws an error
validatePayload[String](Some(Payload("id", Some(5))))
At runtime it's pretty much equivalent to Java's instanceof
operator and a type cast.
TypeTag
But the ClassTag
doesn't work for generic types. For example, sequences with different element types aren't distinguished:
// Doesn't throw
validatePayload[Seq[String]](Some(Payload("id", Some(Seq(1,2,3)))))
If you need to distinguish generic types, you'd have to use TypeTag
s. You must know the type of resource, when you are creating the payload, and the payload must store its Type
or the TypeTag
of its type.
Here is an example:
import reflect.runtime.universe._
case class Payload[T](id: String, resource: Option[T])(implicit val tag: TypeTag[T])
def validatePayload[T : TypeTag](payload: Option[Payload[_]]) = {
payload match {
case None => throw new IllegalArgumentException("Payload was None.")
case Some(p) => p.resource match {
case None => throw new IllegalArgumentException("Resource was None.")
case Some(resource) => resource match {
case temp if p.tag.tpe <:< typeOf[T] =>
case _ => throw new IllegalArgumentException("Resource is not the right type.")
}
}
}
}
Now it will distinguish generics:
// Throws an error
validatePayload[Seq[String]](Some(Payload("id", Some(Seq(1,2,3)))))
But TypeTag
s rely on types known at compile time. So if resource
has type Any
before you create Payload
with it, the validatePayload[T]
will not throw only if T
is Any
. And there are some other quirks:
// Doesn't throw
validatePayload[Seq[Int]](Some(Payload("id", Some(List(1,2,3)))))
// Throws, although resource *is* a List[Int] at runtime
validatePayload[List[Int]](Some(Payload("id", Some(Seq(1,2,3)))))
cast from shapeless
A more robust method is provided by a third-party library shapeless
. Here is an example:
import shapeless.Typeable
import shapeless.syntax.typeable._
def validatePayload[T : Typeable](payload: Option[Payload]) = {
payload match {
case None => throw new IllegalArgumentException("Payload was None.")
case Some(p) => p.resource match {
case None => throw new IllegalArgumentException("Resource was None.")
case Some(resource) => resource match {
case temp if temp.cast[T].isDefined =>
case _ => throw new IllegalArgumentException("Resource is not the right type.")
}
}
}
}
Both don't throw now:
validatePayload[Seq[Int]](Some(Payload("id", Some(List(1,2,3)))))
validatePayload[List[Int]](Some(Payload("id", Some(Seq(1,2,3)))))