scalagenericspattern-matchingexistential-type

scala typemimatch _$1 found instead of required Type


I am using scala 2.11. I have question on the scala generic types and pattern matching.

In the class MyImpl.example(), compiler is unhappy saying type mismatch, Required: _$1, Found: X[_ <: A]

trait A

class B extends A

class C extends A

trait Foo[T <: A] {
  def foo(arg: T): Unit
}

class Bar extends Foo[B] {
  override def foo(arg: B): Unit = print("Bar")
}

class Car extends Foo[C] {
  override def foo(arg: C): Unit = print("Car")
}


class MyImpl {

  def getA(): A = {
    new B()
  }

  def example(): Unit = {
    getFoo("1").foo(getA())
  }

  def getFoo(something: String): Foo[_ <: A] = {
    //return either
    something match {
      case "1" => new Bar()
      case "2" => new Car()
    }
  }
}

object Test {
  def main(args: Array[String]) = {
    new MyImpl().example()
  }
}

Note: getFoo("1") is more dynaminc in my real case, here is just an example.

I kind of understand this, compiler is unable to predict on which pair of the 2 implementation the method is invoked.

I am able to work around this, if I change the implementation of MyImpl.example() to

def example(): Unit = {
    (getFoo("1"), getA()) match {
      case (i: Bar, j: B) => i.foo(j)
      case (i: Car, j: C) => i.foo(j)
    }
  }

I am not really happy with this as I am just repeating i.foo(j)

Any scala functional style writing much cleaner code?


Solution

  • Basically you want something like

    (getFoo("1"), getA()) match {
      case (i: Foo[t], j: t) => i.foo(j) // doesn't compile, not found: type t
    }
    

    or

    (getFoo("1"), getA()) match {
      case (i: Foo[t], j: Id[t]) => i.foo(j) // doesn't compile, t is already defined as type t
    }
    
    type Id[T] = T
    

    You can remove code duplication in

    def example(): Unit = {                    // (*)
      (getFoo("1"), getA()) match {
        case (i: Bar, j: B) => i.foo(j)
        case (i: Car, j: C) => i.foo(j)
      }
    }
    

    (or fix compilation of getFoo("1").foo(getA()))

    with nested pattern matching

    getFoo("1") match {
      case i => getA() match {
        case j: i._T => i.foo(j)
      }
    }
    

    if you add type member _T

    trait Foo[T <: A] {
      type _T = T
      def foo(arg: T): Unit
    }
    

    For getFoo("1") and getA producing new B this prints Bar, vice versa for getFoo("2") and new C this prints Car, for other combinations this throws ClassCastException similarly to (*).

    Please notice that this can't be rewritten as just a variable declaration and single pattern matching

    val i = getFoo("1")
    getA() match {
      case j: i._T => i.foo(j)
    }
    //type mismatch;
    // found   : j.type (with underlying type i._T)
    // required: _$1
    

    because for nested pattern matching the types are inferred correctly (i: Foo[t], j: t) but for val i the type is existential (i: Foo[_] aka i: Foo[_$1] after skolemization, j: _$1).