Why does wrapping a method in another method stop type mismatch in Scala - using underscore in type parameter in pattern match?

Viewed 112

In the following block of code (with both scala 2.11 and 2.12) the method apply does not compile, while applyInlined does.

package blar

trait Bar[T]

class A
class B
class C

trait Exploder[T] {
  // Removing explode and changing Foo so that
  // flatMap takes no param means it will compile
  def explode(product: C): Seq[T]
  val bar: Bar[T]
}

case object Exploder1 extends Exploder[A] {
  def explode(product: C): Seq[A] = ???
  val bar: Bar[A] = ???
}

case object Exploder2 extends Exploder[B] {
  def explode(product: C): Seq[B] = ???
  val bar: Bar[B] = ???
}

object Thing {
  def apply(): Unit = List(Exploder1, Exploder2).foreach {
    case exploder: Exploder[_] =>
      wrapped(exploder)
  }

  def applyInlined(): Unit = List(Exploder1, Exploder2).foreach {
    case exploder: Exploder[_] =>
      flatMap(exploder.explode)(exploder.bar)
  }

  def flatMap[U: Bar](explode: C => TraversableOnce[U]): Unit = ???

  def wrapped[T](exploder: Exploder[T]): Unit =
    flatMap(exploder.explode)(exploder.bar)
}

The error message is

[error] .../src/main/scala/blar/Bar.scala:34:42: type mismatch;
[error]  found   : blar.Bar[_1]
[error]  required: blar.Bar[Object]
[error] Note: _1 <: Object, but trait Bar is invariant in type T.
[error] You may wish to define T as +T instead. (SLS 4.5)
[error]       flatMap(exploder.explode)(exploder.bar)
[error]                                          ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 4 s, completed 03-Jan-2019 13:43:45
  1. My main question is why? Is this a bug?

As you can see applyInlined only differs in that it has inlined the body of the wrapped method. This means that somehow the extra wrapping of some code in a method has "tricked" the compiler into working.

  1. The other question is can you think of a design/hack that would avoid this kind of thing without making Blar covariant? How can I make the inlined version compile? Can I do it with an asInstanceOf

  2. What is scala inferring the type to be in order to call wrapped without an explicit type param?

1 Answers
  1. I don't know. Experimental evidence shows that it has something to do with the presence / absence of the explicit [Exploder[_]] type annotation on List. Without List[Exploder[_]], the inferred type of the list becomes

    List[Product with Serializable with Exploder[_ >: B with A <: Object]]
    

    and for whatever reason, it somehow messes up the subsequent pattern matching. The B with A lower bound looks a little suspicious to me, but I can't explain why it would interfere with the pattern matching.

  2. No, fortunately, no asInstanecOfs are required. Both of the following variants work fine:

    def applyInlined: Unit = List[Exploder[_]](Exploder1, Exploder2).foreach {
      case exploder: Exploder[t] => {
        flatMap(exploder.explode)(exploder.bar)
      }
    }
    

    same with a separately declared implicit variable (note that now you have some type t to refer to):

    def applyInlined2: Unit = List[Exploder[_]](Exploder1, Exploder2).foreach {
      case exploder: Exploder[t] => {
        implicit val bar: Bar[t] = exploder.bar
        flatMap(exploder.explode)
      }
    }
    

    See Type parameter inference in patterns for more about the [t]-part.

  3. I assume it's some synthetic dummy type _1.

Related