Search code examples
scalapattern-matchingmixins

Weird behavior when using pattern matching with mixins


I'm probably missing something obvious, but I can't make this work.

Suppose we have the following code:

object T {
  def method: Unit = {
    trait Mixin
    case class Foo(foo: String)

    val f = new Foo("hi") with Mixin
    f match {
      case Foo(s) => println(s)
    }
  }
}

This compiles and prints "hi" when I call Test.method. The pattern match code can even be inside nested methods or nested functions, and it works. However, if I move the case class outside of method and directly attached to T2:

object T2 {

  case class Foo(foo: String)

  def method: Unit = {
    trait Mixin

    val f = new Foo("hi") with Mixin
    f match {
      case Foo(s) => println(s)
    }
  }
}

it throws the following error:

Error:(183, 12) constructor cannot be instantiated to expected type;
 found   : T2.Foo
 required: T2.Foo with Mixin
      case Foo(s) => println(s)
           ^

If now I don't use Mixin, and only make val f = new Foo("hi"), it works fine again. It also works if I try to match on its type:

val f = new Foo("hi") with Mixin
f match {
  case f: Foo => println("I am a Foo")
}

Why doesn't T2 work, if the case class (and all of its generated methods) are in scope, and why would it matter where it is defined? In my real code, I have several case classes, in several modules, and the pattern matchers are all over the place, so I can't just move everything inside the same method, and I'd prefer if I don't have to get rid of the mixins, so what other alternative do I have?


Solution

  • I believe there are a few things going on.

    • The Scala compiler is trying to figure out what types you are matching on.
    • You've mixed a top level type with a local type, which seems to confuse the compiler.

    In T, both Foo and Mixin are declared locally in the scope of your method. A local type isn't necessary available outside of its scope (in this case, method). Because everything is in the same scope, it's easy for the compiler to figure out what you're trying to do.

    In T2, Foo is declared at the top level but Mixin is still a local type. This seems to throw off the compiler. You can help the compiler by specifically typeing value f to Foo, which will allow your snippet to compile.

    object T2 {
      case class Foo(foo: String)
      def method: Unit = {
        trait Mixin
        val f: Foo = new Foo("hi") with Mixin
        f match {
          case Foo(s) => println(s)
        }
      }
    }
    

    I wish I could give a more detailed (and guaranteed accurate) explanation.