Search code examples
scalatypestype-inferencetype-safetyexistential-type

Aux-pattern usage compiles without inferring an appropriate type


Consider the following simple example involving Aux-pattern:

sealed trait AdtBase

abstract case class Foo(){
  type T <: AdtBase
}

object Foo{
  type Aux[TT] = Foo { type T = TT }
}

abstract case class Bar(){
  type T <: AdtBase
  val foo: Foo.Aux[T]
}

object Bar {
  type Aux[TT] = Bar { type T = TT }

  def apply[TT <: AdtBase](f: Foo.Aux[TT]): Bar = new Bar() {
    override type T = TT
    override val foo: Foo.Aux[T] = f
  }
}

case class Baz(foo: Foo)

def testBaz(baz: Baz) = Bar(baz.foo) //Compiles fine
def testFoo(foo: Foo) = Bar(foo) //Error: Type mismatch

Scastie

I don't really understand why testBaz compiles. I expected type mismatch as well.


Solution

  • It seems there is no deep reason for that.

    Since when you specify type parameter explicitly both methods compile

    def testBaz(baz: Baz) = Bar[baz.foo.T](baz.foo) //compiles
    def testFoo(foo: Foo) = Bar[foo.T](foo)         //compiles
    

    it seems in

    def testBaz(baz: Baz) = Bar(baz.foo) //compiles
    //def testFoo(foo: Foo) = Bar(foo)   //doesn't compile
    

    in the first case the type baz.foo.T is inferred while in the second case the type foo.T is just not inferred

    // found   : Foo
    // required: Foo.Aux[this.T]
    

    In Scala it's always possible that some type parameter will not be inferred and you'll have to specify it explicitly.


    Maybe I found a possible reason.

    The code

    class testFoo2(foo: Foo) {
      // Bar(foo) // doesn't compile
    }
    

    doesn't compile but if you make foo a val

    class testFoo2(val foo: Foo) {
      Bar(foo) // compiles
    }
    

    then it does. Probably the thing is that when foo is a val it's more "stable" and in such case it's "easier" to infer path-dependent type foo.T.

    So the difference between testBaz and testFoo can be that Baz is a case class so foo is a val while in testFoo foo is just a method parameter and therefore less "stable".

    Similarly , on contrary to

    trait A[T]
    def m[T](a: A[T]) = ???
    m(??? : A[_]) // compiles
    

    the code

    trait A { type T } 
    def m[_T](a: A { type T = _T}) = ??? 
    m(??? : A) // doesn't compile
    

    doesn't compile but if we extract a variable

    val a: A = ???
    m(a) // compiles
    

    then it does. The thing is that now a is stable and type a.T can be inferred.