(Scala 2.12.8) Full Example
So lets say you have some "TypeEvidence" for some specific concrete types:
sealed trait TypeEvidence[ T]
object TypeEvidence{
case object DoubleType extends TypeEvidence[Double]
case object LongType extends TypeEvidence[Long]
case object StringType extends TypeEvidence[String]
}
I can match on those evidence objects like this:
object ThisIsOk{
def apply[T](ev: TypeEvidence[T]): Option[T] = {
ev match {
case TypeEvidence.DoubleType => Some(123.456)
case TypeEvidence.LongType => Some(1234L)
case _ => None
}
}
}
But not like this:
class ThisFails[T]{
def apply(ev: TypeEvidence[T]): Option[T] = {
ev match {
case TypeEvidence.DoubleType => Some(123.456)
case TypeEvidence.LongType => Some(1234L)
case _ => None
}
}
}
This fails to compile with:
pattern type is incompatible with expected type;
found : TypeEvidence.DoubleType.type
required: TypeEvidence[T]
pattern type is incompatible with expected type;
found : TypeEvidence.LongType.type
required: TypeEvidence[T]
Why is this? And how can this be worked around?
CodePudding user response:
What if you do this with a type class?
class ThisFails[T]{
def apply(ev: TypeEvidence[T])(implicit
teto: TypeEvidenceToOption[T]
): Option[T] = teto(ev)
}
trait TypeEvidenceToOption[T] {
def apply(ev: TypeEvidence[T]): Option[T]
}
object TypeEvidenceToOption {
implicit val double: TypeEvidenceToOption[Double] =
{ case TypeEvidence.DoubleType => Some(123.456) }
implicit val long: TypeEvidenceToOption[Long] =
{ case TypeEvidence.LongType => Some(1234L) }
implicit def default[T]: TypeEvidenceToOption[T] = _ => None
}
new ThisFails[Double].apply(TypeEvidence.DoubleType) // Some(123.456)
new ThisFails[Long].apply(TypeEvidence.LongType) // Some(1234)
new ThisFails[String].apply(TypeEvidence.StringType) // None
CodePudding user response:
As a workaround, you can use .type
and .asIstanceOf
:
class ThisFails[T] {
def apply(ev: TypeEvidence[T]): Option[T] = {
ev match {
case _: TypeEvidence.DoubleType.type => Some(123.456.asInstanceOf[T])
case _: TypeEvidence.LongType.type => Some(1234L.asInstanceOf[T])
case _ => None
}
}
}
val x = new ThisFails[Long]
val x2 = new ThisFails[Double]
val x3 = new ThisFails[String]
println(x.apply(TypeEvidence.LongType)) // Some(1234)
println(x2.apply(TypeEvidence.DoubleType)) // Some(123.456)
println(x3.apply(TypeEvidence.StringType)) // None
I'm aware it's not nice, but you asked for a workaround. This pattern match now matches an object of the given type. Of course, with this workaround, having the objects "caseable" is redundant now.
I had a peek at the disassembled code of object ThisIsOk
to figure out this workaround:
public static class ThisIsOk$
{
public static final ThisIsOk$ MODULE$;
static {
ThisIsOk$.MODULE$ = new ThisIsOk$();
}
public <T> Option<T> apply(final Main.TypeEvidence<T> ev) {
Object module$;
if (Main.TypeEvidence$.DoubleType$.MODULE$.equals(ev)) {
module$ = new Some((Object)BoxesRunTime.boxToDouble(123.456));
}
else if (Main.TypeEvidence$.LongType$.MODULE$.equals(ev)) {
module$ = new Some((Object)BoxesRunTime.boxToLong(1234L));
}
else {
module$ = None$.MODULE$;
}
return (Option<T>)module$;
}
}
I saw here the way pattern match works is by calling equals
on their types, but since equals
is not overridden, what this actually does is a reference check. Recall:
// in Object.java file:
public boolean equals(Object obj) {
return (this == obj);
}
Also I saw a cast to Option<T>
in the end.
So with these observations, the class ThisFails[T]
workaround I just showed earlier gets disassembled into this:
public static class ThisFails<T>
{
public Option<T> apply(final Main.TypeEvidence<T> ev) {
Object module$;
if (Main.TypeEvidence$.DoubleType$.MODULE$ == ev) {
module$ = new Some((Object)BoxesRunTime.boxToDouble(123.456));
}
else if (Main.TypeEvidence$.LongType$.MODULE$ == ev) {
module$ = new Some((Object)BoxesRunTime.boxToLong(1234L));
}
else {
module$ = None$.MODULE$;
}
return (Option<T>)module$;
}
}
Which is almost identical to the object ThisIsOk
's disassembled code, except for using ==
instead of equals
, which does not matter in this case, as I explained earlier.
I tested it with both Scala 2.13.8 and your version here on Scastie, and both worked.