Generic type inference

133 views
Skip to first unread message

Brad Micholson

unread,
May 26, 2015, 5:52:25 PM5/26/15
to google...@googlegroups.com
Is there currently any way to use type inference in an assisted inject factory?
For example, is there any way to use an interface like the following as an assisted inject factory?


interface FooFactory
{
  Foo<T> create(T bar);
}

Nate Bauernfeind

unread,
May 26, 2015, 6:08:55 PM5/26/15
to google...@googlegroups.com
Assuming you meant:
interface FooFactory {
  <T> Foo<T> create(T bar);
}

No, this does not work. It would require one-to-many bindings, which, if you read the recent forum's history, is not supported. 

This is the closest you can get. I use it pretty frequently:
interface FooFactory<T> {
  Foo<T> create(T bar);
}

You will need to use type literals to capture the generic types when you create the assisted inject factories. (See: http://google.github.io/guice/api-docs/latest/javadoc/index.html?com/google/inject/TypeLiteral.html)

Nate

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.
To post to this group, send email to google...@googlegroups.com.
Visit this group at http://groups.google.com/group/google-guice.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-guice/3b354f12-1927-44fb-ac0d-94b28941ced6%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Sam Berlin

unread,
May 26, 2015, 6:16:21 PM5/26/15
to google...@googlegroups.com
The "many to one binding" thing isn't really the reason since it's not a binding, it's just a method call in your factory.

For assistedinject, the lack of being able to specify <T> on the method w/o also specifying it on the factory is really just a no one's had the time to sit down and figure out how to do it.  See the bug @ https://github.com/google/guice/issues/218 .


sam

Nate Bauernfeind

unread,
May 27, 2015, 1:08:32 AM5/27/15
to google...@googlegroups.com
Sam, I don't see how it could ever work if T itself had generic parameters. I can only see two solutions 1) push the burden onto the client of the factory and force them to provide a type literal or 2) the generic factory methods only work for a subset of types. In my opinion option 2 is useless: you should be able to bind Foo[List[String]] to a different implementation than a Foo[List[Int]] for example.

The current solution is to add the type parameter to the factory, which requires a one-to-one binding per factory. I thought that people don't like this option because they'd like to remove the one-to-one binding restriction, or perhaps they don't want to inject `n` different Factories someplace. (There might be other advantages I'm being shortsighted about; these are just the two that pop out as large costs.)

Removing these particular restrictions is clearly a one-to-many binding argument. In fact, a non one-to-many implementation is relatively straightforward! Here's an example in scala (for terseness):

object T extends App {
  class Foo[T] (@Assisted t: T) { override def toString = s"Foo($t)" }
  class FooString @Inject()(@Assisted t: String) extends Foo[String](t)
  class FooInt @Inject()(@Assisted t: Integer) extends Foo[Integer](t)
  
  trait F {
    def create(t: String): Foo[String]
    def create(i: Int): Foo[Integer]
  }
  
  val mod = new ScalaModule {
    def configure(): Unit = {
      install(new FactoryModuleBuilder()
        .implement(typeLiteral[Foo[String]], typeLiteral[FooString])
        .implement(typeLiteral[Foo[Integer]], typeLiteral[FooInt])
        .build(typeLiteral[F]))
    }
  }
  
  import net.codingwell.scalaguice.InjectorExtensions._
  val injector = Guice.createInjector(mod)
  println(injector.instance[F].create(42))
  println(injector.instance[F].create("hello world"))
}

There are a couple of issues that you can't really get around. 1) You need to subclass Foo or else the constructor loses the parameter's type information due to erasure. 2) you need to enumerate each create method in the factory. 3) you need one implement call per T in your factory. And subtly, 4) the method `def create[T](t: T): Foo[T]` doesn't exist! 

This is three times as much work as would be nice, but due to JVM restrictions I don't think you can remove the additional work without requiring TypeReferences on the create method. 

Does this shed light from my perspective, or am I misunderstanding the problem?

Sam Berlin

unread,
May 27, 2015, 7:58:47 AM5/27/15
to google...@googlegroups.com

If <T> is only used as an assisted param (and *not* used by any injected ones), then I think we can get away with just ignoring it. The compiler will make sure the types are safe, and we can just pretend T is its erased type.  So long as the user supplies us the T and we don't have to worry about locating anything that references a T... there won't be any unknowns.

sam


Brad Micholson

unread,
May 27, 2015, 9:01:16 AM5/27/15
to google...@googlegroups.com
Assuming you meant:
interface FooFactory {
  <T> Foo<T> create(T bar);
}

Ah, yes. That's of course what I meant.
 
This is the closest you can get. I use it pretty frequently:
interface FooFactory<T> {
  Foo<T> create(T bar);
}

I'm guessing I would still need to know the generic type, or at least the possible types, to bind that in the Module...

Nate Bauernfeind

unread,
May 27, 2015, 10:54:30 AM5/27/15
to google...@googlegroups.com
I must still be missing the point, Sam. What would you expect the Guice module to look like?

Is this your understanding of what the feature is:
interface Foo<T> {
  ...
}

interface FooGuiceFactory {
  Foo<Object> create(Object o)
}

Currently, you could then write an instance of the interface you really want:
class FooFactory {
  @Inject private final FooGuiceFactory fooFactory;

  <T> Foo<T> create(T t) {
    return (Foo<T>)fooFactory.create(t);
  }
}

That seems underwhelming compared to what I was imagining. It also seems like it would be pretty trivial to implement -- If you think people would use it I would be interested in giving it a shot.

Sam Berlin

unread,
May 27, 2015, 11:20:06 AM5/27/15
to google...@googlegroups.com
I guess the module would only work if:
      a) The user didn't specify an implementation type for Foo
  or b) The implementation type had the exact same generic args as Foo.

So the workable scenarios would be:

a)
  class Foo<T> { 
    interface Factory {
      Foo<T>  create(T t);
    }
  }
  Module {
    configure() {
       install(new FactoryModuleBuilder().build(Foo.Factory.class));
    }
 }

b) 
  // same Foo + FooFactory from A
  class FooImpl<T> extends Foo<T> { ... }
  Module {
    configure() {
      install(new FactoryModuleBuilder().implement(Foo.class, FooImpl.class).build(Foo.Factory.class);
    }
 }


While (b) is technically workable, validating the generics are the same is a pretty hard problem. 
(a) is much easier to do, but would require adding validation to fail properly in the scenarios we can't handle (right now the validation catches (a) too... loosening it to allow (a) but disallow (b) and everything else becomes a harder problem).

sam

Reply all
Reply to author
Forward
0 new messages