scalagenericsgeneric-variance

Generic typing so that one method's result can be used as other method's parameter again


I have code that boils down to a Factory initializing an object and then using that object again to do additional operations:

trait Factory[T] {
  def initialize(): T;

  def finish(t: T): Unit;
}

As I understand this, the result of initialize should always be suitable to be passed to finish for any one Factory instance, regardless of T.

The factory itself is called at a point where it isn't known what T is:

object Minimal {
  object StringFactory extends Factory[String] {}
  val factories = Map[Int, Factory[_]](0 -> StringFactory)

  val factory = factories(0)

  // (1)
  val obj = factory.initialize()
  factory.finish(obj)

  // (2)
  def wrapper[T](factory: Factory[T]): Unit = {
    val obj = factory.initialize()
    factory.finish(obj)
  }
  wrapper(factory)
}

While variant (2) works, variant (1) doesn't:

type mismatch; found : Minimal.obj.type (with underlying type Any) required: _$6

but I can't figure out how to fix this. Is it even possible?

What does the compiler get by calling the wrapper method that it can't figure out itself? From my point of view, obj's type should be _$6, as the compiler seems to name that capture of _. How can I make the compiler realize that without having to introduce a whole new method for it?


Solution

  • Based on Régis' answer, I found out that the compiler infers obj: Factory.T. From there, it was a small step to combine this with dk14's suggestion to use type TT = T. The result is this, buth generic and statically typechecked, without introducing a wrapper method. Kudos to both!

    To literally answer the original question

    From my point of view, obj's type should be _$6, as the compiler seems to name that capture of _. How can I make the compiler realize that without having to introduce a whole new method for it?

    by giving _$6 the explicit name TT. Of course, the methods then need to use that name as well.

    trait Factory[T] {
      type TT = T
      def initialize(): TT;
    
      def finish(t: TT): Unit;
    }
    
    object Minimal {
      object StringFactory extends Factory[String] {
        def initialize(): TT = ""
        def finish(t: TT): Unit = {}
      }
      val factories = Map[Int, Factory[_]](0 -> StringFactory)
    
      val factory = factories(0)
    
      // (1)
      val obj: factory.TT = factory.initialize()
      factory.finish(obj)
    
      // (2)
      def wrapper[T](factory: Factory[T]): Unit = {
        val obj = factory.initialize()
        factory.finish(obj)
      }
      wrapper(factory)
    }