Home > OS >  (Scala 2.12.8) pattern type is incompatible with expected type for parameterized type inside of para
(Scala 2.12.8) pattern type is incompatible with expected type for parameterized type inside of para

Time:09-09

(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.

  • Related