3

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?

Silly Freak
  • 4,061
  • 1
  • 36
  • 58
  • How can you .initialize() a null variable? – Jérôme Mahuet Jan 15 '15 at 08:57
  • that's dummy, code, therefore the comment above that line. But the code doesn't compile. I'm talking about the compiler's behavior, not about an NPE – Silly Freak Jan 15 '15 at 09:01
  • 3
    Well, in (1) you don't have a `Factory[T]` but a `Factory[_]`. – Jasper-M Jan 15 '15 at 09:11
  • @JérômeMahuet I changed the code to avoid that misunderstanding – Silly Freak Jan 15 '15 at 09:13
  • @Jasper-M I improved the question. Yes, it is a Factory[_], obviously. My experience with Scala's type inference is that it can figure out a lot, but it can't figure out that `finish`'s parameter is the same as `initialize`'s return type, despite it being in the trait declaration. I guess I could as well ask, why is `obj`'s type `Any` and not `_$6`? – Silly Freak Jan 15 '15 at 09:25
  • do you need to generate or modify your set of factories dynamically? or you want to iterate over them? or why would you store them in a dynamic structure such as a Map? – Erik Kaplun Jan 15 '15 at 09:58
  • The usecase is to provide de/serialization adapters for different types based on a type ID, specifically Protobuf extension IDs. Once set up, the map won't change anymore, but it needs to be set up once, and I need random access based on the ID – Silly Freak Jan 15 '15 at 10:09

3 Answers3

3

Existential type looses its existentiality and becomes upper-bound after assigning its instance to val itself, so any way without such assigning will work, including:

 scala> trait Factory[T] { type TT = T; def initialize(): TT; def finish(t: TT): Unit;}
 defined trait Factory

 scala> val factory: Factory[_] = new Factory[Int] {def initialize = 5; def finish(t: Int) {}}
 factory: Factory[_] = $anon$1@31d0ca61

 scala> factory.finish(factory.initialize())

This will not work:

scala> val obj = factory.initialize()
obj: Any = 5

scala> factory.finish(obj)
<console>:11: error: type mismatch;
 found   : Any
 required: factory.TT
    (which expands to)  _$1
              factory.finish(obj)
                             ^

And this because scala will not see their types as equal (unless both are one same type member) as existentiality means that intialize() may return any subclass of Any, when finish() may accept any (but not always same) subclass of Any:

scala> trait Factory[T] { def initialize(): T; def finish(t: T): Unit;}
defined trait Factory

scala> val factory: Factory[_] = new Factory[Int] {def initialize = 5; def finish(t: Int) {}}
factory: Factory[_] = $anon$1@6e5da49

scala> factory.finish(factory.initialize())
<console>:10: error: type mismatch;
 found   : (some other)_$1(in value factory)
 required: _$1(in value factory)
              factory.finish(factory.initialize())
                                               ^

So the only way to bind input and output here is sharing type member between them.

dk14
  • 22,206
  • 4
  • 51
  • 88
  • Actually if you remove the type parameter this would work. See my answer – Régis Jean-Gilles Jan 15 '15 at 09:38
  • So introducing `type TT = T` fixes this when the compiler can stay within the class while typechecking, but that type is lost when the compiler has to give `obj` a definite type, I see... seems I have to introduce that method after all :\ thanks! – Silly Freak Jan 15 '15 at 09:39
  • @ Régis Jean-Gilles there is no existential type in your solution: it's just same `Factory` everywhere – dk14 Jan 15 '15 at 09:45
  • 1
    @RégisJean-Gilles too bad I can only accept one answer, because a combination of both yours got me a solution, maybe one of you can incorporate that: declare `obj` as `val obj: factory.TT`! – Silly Freak Jan 15 '15 at 09:46
  • Done. Though in this case inference works like a charm and the type annotation is not mandatory. – Régis Jean-Gilles Jan 15 '15 at 09:50
  • @dk14 check out my answer, it combines your and Régis ideas to solve the problem. Thanks, your input was invaluable! – Silly Freak Jan 15 '15 at 10:06
2

One solution would be to entirely replace the type parameter with an abstract type:

trait Factory {
  type T
  def initialize(): T;

  def finish(t: T): Unit;
}

object Minimal {
  object StringFactory extends Factory { 
    type T = String
    def initialize(): T = ???
    def finish(t: T): Unit = ??? 
  }
  val factories = Map[Int, Factory](0 -> StringFactory)

  val factory: Factory = factories(0)

  // (1)
  val obj: factory.T = factory.initialize()
  // Or simply (relying on inference): val obj = factory.initialize()      
  factory.finish(obj)

  // (2)
  def wrapper(factory: Factory): Unit = {
    val obj = factory.initialize()
    factory.finish(obj)
  }
  wrapper(factory)
}
Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
1

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)
}
Community
  • 1
  • 1
Silly Freak
  • 4,061
  • 1
  • 36
  • 58