Dependency injection via parameterized types (?!)

Say we have a C#-like language that keeps runtime information about type arguments. Then we can say something like:

class Dependent<T> {
  void doIt() { new T(); }
}

class Dependency {
}

// "wire" our cool dependency network
Dependent d = new Dependent<Dependency>();

// creates a new instance of the Dependency
d.doIt();

Doesn't this subsume almost all applications of DI? The top-level of an application then becomes a big NEW expression, that wires the DI network. For mock-testing, one would use a different statement:

// normal application
new MyApplication<MyService1Impl, MyService2Impl, ...>();
// for testing:
new MyApplication<MyService1Mock, MyService2Mock, ...>();

Now, I can see that sometimes one doesn't want to create a fresh instance of some dependency, but rather get an existing instance from an "object broker", so plain NEW is the wrong thing. But many object-oriented Lisp dialects already have a solution for this, too: NEW is simply a virtual function, that can be overridden on a per-class basis [e.g. Dylan's make]. This allows one to actually interpret any "new T()" expression as an ordinary call of a function, that could then use reflection to get an instance of the actual class T from the broker.

Am I missing something here, or do parameterized types offer a full solution to dependency injection?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Objects as Modules

Your question reminds me of an earlier discussion: Objects as Modules in Newspeak.

It seems to me that you're looking a little too deeply for the answer to DI. You've already assumed polymorphism. If you have polymorphism, you can describe abstract factories that produce abstract base classes (aka signatures or interfaces). class ABCFactory { public: ABC create(args) = 0; }; You would parameterize a dependent's constructor with an ABCFactory wherever you might have otherwise parameterized the dependent's type with an ABC-compatible type.

The real issue with DI, however, is making it relatively convenient - i.e. placing the flexibility and power of DI on the path of least resistance for developers. I think that Newspeak has the right idea there: eliminate concrete imports for module definitions, and eliminate ambient authority (including global data). Each of those design choices forces greater parameterization of dependencies... i.e. it puts DI on the path of least resistance by the expedient means of raising resistance to all other paths.

Hm...

You would parameterize a dependent's constructor with an ABCFactory wherever you might have otherwise parameterized the dependent's type with an ABC-compatible type.

I'm exactly proposing to eliminate this - IMHO - boilerplate (which is the reason for DI frameworks in the first place) by exploiting the ability of parameterized types to remember and inspect their type arguments at runtime. The dependent does a "new T()" on a type it's parameterized over, and that's it.

The real issue with DI, however, is making it relatively convenient

I find my proposal extremely convenient:

  • there's no runtime factory stuff - instead parameterization happens statically at the type level;

  • dependents use a simple NEW to instantiate dependencies, and as Dylan et al. show this can be extended seamlessly to "hash-cons" or broker objects;
  • dependency "wiring" amounts to a humble NEW expression, that creates the "application" and parameterizes it on the type level with its dependencies.

[Edit: That's simply the most convenient definition of DI I can imagine, and that's why I put the exclamation mark in the original post's title.]

Waterbed Theory

I'm exactly proposing to eliminate this - IMHO - boilerplate

But you didn't. You just moved it from

class Dependent { Dependent(HERE); };

to

template<HERE> class Dependent { Dependent(); };.

The information and any static data management you encode into these type parameters and specialized new methods will become boiler-plate.

there's no runtime factory stuff

Sure there is. You've moved that job to the 'class' objects, and you answer complexity by overloading virtual new methods. This isn't very flexible: you can make many instances of a factory with different parameters, but you have only one static instance of a class - and so need a lot more classes to accomplish the same flexibility as the normal factory pattern. But they are factories - complete with runtime 'static' data.

This is why it reminded me of Objects as Modules, where class objects are first-class, and serve as first-class factories. (You can create many different instances of a class object.)

This isn't very flexible:

This isn't very flexible: you can make many instances of a factory with different parameters, but you have only one static instance of a class - and so need a lot more classes to accomplish the same flexibility as the normal factory pattern.

Point taken. As I've written to Jules, I brought this topic up mostly out of curiosity - I think it's nice that I can do this with exact runtime types, I'm not sure if I want to do it. ;)

Dependency injection in

Dependency injection in languages where classes are values is even more trivial: you can pass the class as an ordinary parameter instead of as a type parameter. Dependency injection frameworks seem to be just a way to get around this limitation in languages that don't support "first class classes".

Compile-time and link-time optimizations

One big benefit of second-class classes is that you can do a lot of static analyses early at compile-time, or later at link-time, saving you potentially a lot of work at runtime.

[Update: Given that all parameterized classes and their actual instantiations are known at link-time at the latest, it becomes possible to for example populate method lookup tables (like C++) at link-time. This sometimes important optimization is made impossible by first-class classes.]

I don't think first class

I don't think first class classes inhibit these optimizations. You can even add first class classes to a languages without them. A first class class supports two operations:

  1. Make a new objects of its type.
  2. Check whether an object is an instance of it.

So if you have a class Foo, you create a class FooClass:

class FooClass {
  Foo Make(){ return new Foo; }
  bool IsInstance(object x){ return x instanceof Foo; }
}

Or am I missing something?

Static

If you have second-class classes (i.e. you don't allow treating classes as values, and thus don't have to bother about dynamic control flow/escape analysis), some analyses become possible before running the program, statically at link-time, which I think is important for some languages and deployments (those that don't want to carry their compiler and linker with them at runtime).

For example, the whole class hierarchy/heterarchy is known (enables efficient subtype tests), and all methods are known (enables efficient dispatch tables, inlining, ...) statically.

You repeated the same point,

You repeated the same point, but I don't believe that what you say is true. You can retroactively add first class classes as a simple preprocessing step as I demonstrated. This doesn't make the analyses you mention impossible at all? The class hierarchy and method tables can still be statically determined.

Ah

Sorry, I misunderstood you, and I think you are right, yes.

For completeness' sake, I still prefer the model in the original post, since I'm assuming parameterized types and exact runtime types anyway, and it saves me the need to have to store and pass around classes/factories explicitly at runtime.

I just thought it's cute that you can do this with exact runtime types, I've personally never actually had a need for DI.

C# like language?

This is already what C# effectively does for DI in Unity and its kin. You need to make a type constraint so that T becomes vaguely usable. And most times the injected type is pulled from config, resolving that type and instantiating the generic in the first place involves quite a bit more boilerplate than you seem to indicate here.

Jules Jacobs point is pretty solid. No reason to go through the hoops of this generic structure when the language already has a Type erm... type. Allow some sort of Kinding constraint on type values and you're good to go.

Artefact of C# model?

And most times the injected type is pulled from config, resolving that type and instantiating the generic in the first place involves quite a bit more boilerplate than you seem to indicate here.

Assumption: Because C# is modelled as a compiler, and offers no interactive way to load source files, it's probably necessary to use a separate config file. In an interactive language, a simple main file that contains the new expressions would be the "config" file, I think.

You only ever instantiate

You only ever instantiate classes in main? Your main knows about all of the different libraries the code will ever use? You're then going to duplicate all of the other code in the main source code file when you want to swap concrete versions?

No, this seems like it would defeat all of the benefits of DI.

If you renamed the "main"

If you renamed the "main" file "config", would that better match your mental model of DI?

The way I see it, so long as the "main" file didn't include any code apart from the configuration and instantiation of the rest of the application, it could function as a DI configuration.

The trick to think about DI...

...is to realize that every dependency injection configuration file _could_ be a file in your implementation language, if only that language was powerful enough. Then try to imagine how powerful your language would have to be in order to make that work.

Seriously, the call point needn't be "main". It could just as easily be "MyServletFilter.init()", or "MyContainer.onInitialize()" or even "MySession.load()". This is just a way of indicating that _most_ of the logic in an average (enterpise?) application is embedded in singletons loaded in bulk at some "start" time. This deviates for object-oriented orthodoxy, but is nonetheless manifestly true.

Re-editable Configurations and Parameterized Modules

Why do you believe that "duplicating all of the code in the main source file when you want to swap concrete versions" would "defeat all the benefits of DI"? Which benefits does it defeat?

A previous LtU discussion on Parameterized Modules references Bracha's blog posts A Ban on Imports and A Ban on Imports (continued). These posts describe how "import confounds module definition and module configuration", requiring us to hand-edit library code or utilize language preprocessors for 'drop-in replacements'. Another post, Cutting out Static and the ensuing discussion describes various issues (re-entrancy, distribution, concurrency, testing, security, garbage-collection, order-of-initialization) that arise when libraries have direct access to a shared environment (such as static class variables, singletons, FFI, embedded assembly) - which also suggests that configuration (of authority and shared data elements) should flow from a well-defined center.

My own experience with C++ has me tending to favor template code just to avoid concrete dependencies between classes.

It seems there is plenty of motivation for 'configuration' - of libraries and authorities both - to be separated from their 'definition'. Perhaps our GPPLs should be similarly bifurcated - into re-editable configurations and parameterized modules.

It seems to me that, if we wish to avoid issues with statics and imports, that a reasonable approach is to bifurcate our GPPLs into library and configuration sublanguages. We could achieve both 're-editable configurations' and 'reusable libraries'. Access to FFI would be provided through objects available only to the configuration language... e.g. via powerbox.

Benefits

(unless I'm missing something, which is always a distinct possibility)

Which benefits does it defeat?

It associates your application setup/kickoff with the implementation details of what you're trying to inject.

It requires duplication of that setup/kickoff code, or some similar configuration to what already exists when you want variation.

Worst of all, in a language like C# (assuming no configuration/scripts or sublanguage like you describe) it would require recompilation of the project (including a static reference to the dependency you're injecting). That is a deal breaker in most practical applications of the technique.

Re: Benefits

It associates your application setup/kickoff with the implementation details of what you're trying to inject.

I'm trying to understand the assumptions under which this claim might be true. You certainly understand that instantiating a class by name does not mean specifying the implementation-details of that class... so perhaps you view the class name itself to be an 'implementation detail' that you wish to decouple from the application setup code.

If so, then maybe you would rather describe 'services' to be provided in terms of names or contracts, and utilize some sort of match-maker or registry that can find an implementation. You might need one object to provide you SVG glyphs for the unicode character set, another object to provide filesystem access, a third object to provide a clock, and yet another object to provide you a list of observable joysticks. You rather not specify which 'class' is to provide these services.

I do see these as valid points that need to be addressed for the language, and that would effectively reject 'class instantiation' as a basis for FFI integration.

But if we were to assume an object capability language (as would be the case if we 'cut out the static') then class instantiation would be separate from FFI integration in any case. We'd be using instead a technique such as a powerbox to provide various generic and utility services.

It requires duplication of that setup/kickoff code, or some similar configuration to what already exists when you want variation.

It is true that duplication is required. However, you ought compare this to the amount of duplication (lines of code) required when you need to tweak a configuration that is distributed throughout a library.

If only a central 'main' or project file can explicitly import classes by name, then only the 'main' file ever needs duplicated and tweaked in order to produce a variation on a project.

OTOH, if configuration is distributed throughout the library code (due to libraries making concrete references to other libraries) then you must duplicate and tweak every library on the path between 'main' and the module you are modifying. For a distributed configuration the 'main' file is likely much smaller (since it doesn't need to configure as many libraries), but the total cost of producing and maintaining a variation is often going to be far worse.

This point is expressed also in Bracha's 'Ban on Imports' blog entries.

Worst of all, in a language like C# [...] it would require recompilation of the project

I do not see how this is the case.

As I understand it, C# generics have full runtime support; the referenced libraries need not be recompiled for a different parameterization. And, certainly, this would be more the case were the language developed for a rapid edit-test cycle with fully centralized configuration. Full class specialization may be elided except, optionally, for a 'release' compile.

Satisfying Contracts

If so, then maybe you would rather describe 'services' to be provided in terms of names or contracts, and utilize some sort of match-maker or registry that can find an implementation.

Indeed, and this matchmaking is commonly/currently done in C#-like languages via configuration file that specifies the implementation of a contract by name/assembly/version.

While the language supports runtime parametrization of generics, that is not what I understood the original post's intent to be. And indeed, runtime parametrization of generics seems to be an extra step when you could just pull up the correct implementor of the contract and instantiate that directly upon request.

But you still have to know what the implementor of the contract is. Commonly this is done via config file and by name. There are frameworks that use Attributes and then priority/matching mechanisms to satisfy the request.

I understood the original post to mean that this mapping would be done in code by explicitly supplying the implementor class name to a generic. I also understood that normal C#-like identifier resolution rules would be used to handle that, and that no preprocessor shenanigans are expected to optionally include parts depending on build flags.

Under these constraints/assumptions, you would either still need some sort of config file or a recompile to switch implementors. ...unless I'm missing something of course.

I don't at all disagree with the possible problems with duplicating configuration across modules, most module systems currently or the approaches you listed to address those. I'm just saying the original proposal doesn't at first glance appear to be a benefit.

The follow up that an interactive language (unless I'm misunderstanding the intent) makes it better seems incomplete. Interaction at runtime is highly impractical, or would be quickly scripted into something akin to a config file. Interaction at compile time is less impractical, but would still be scripted into something akin to a config file. I don't see the benefit there.

Elusive Context

I read your earlier post as making a conclusion for just the three assumptions you explicitly listed:

  1. You only ever instantiate classes in main.
  2. Your main knows about all of the different libraries the code will ever use.
  3. You're then going to duplicate all of the other code in the main source code file when you want to swap concrete versions.

This [set of three constraints] seems like it would defeat all of the benefits of DI.

That conclusion did not follow. But your assertion that you intended to also include various constraints and assumptions from the earlier posts clears up much confusion on my part.

If you're grokking 'interactive language' in the sense of 'interactive theorem proving' - where a user may be required to achieve progress - then I see why you might be confused by the latter claims. I suspect Manuel intended 'interactive language' more along the lines of Lisp, Perl, Ruby - languages that traditionally provide a REPL and manage user-defined types at runtime. Thus, I understand the runtime support for generics to be quite relevant.

A few of your earlier points were peculiar to C# (which Manuel noted with 'Artifact of the C# model?'). There is no fundamental reason that instantiating a generic at runtime should be difficult or even distinct from doing so at compile-time.

CLR generics

dmbarbour: As I understand it, C# generics have full runtime support; the referenced libraries need not be recompiled for a different parameterization.

The implementation of CLR generics does require some amount of load- or run-time code generation in any case. From section 1.3 of the paper:

“Just-in-time” type specialization. Instantiations of parameterized classes are loaded dynamically and the code for their methods is generated on demand.

I think they have to do this because they support variably-sized, non-reference data types (e.g. int) as type arguments.

It looks like you're just

It looks like you're just lifting all dependencies to the top-level, where you must instantiate them explicitly. That can get pretty cumbersome pretty quickly.

Of course, some of the dependencies will be instantiated from other dependencies, so if organized properly it may not be so bad, and indeed I try to do things this way instead of relying on DI. Still, would be nice to have this all be inferred, ala "implicit configurations".

Yes! Due to some health

Yes! Due to some health issues, I'm making zero progress lately, but I've been intrigued by the idea of a (simple?) set of default implementation-to-interface bindings (signatures/modules, abstract classes/objects, perhaps some other structurally typed "duck typing" construct like Scala's objects - dunno).

This might well make typical programming nearly burden free, but still expose a more flexible configuration of dependencies where needed. Ah, a man can dream, and minus neurological burdens, even eventually configure a reasonable design/implementation of this general idea. Sigh....

Related question. If we have

Related question. If we have a language with a "typical" object system (is parametric polymorphism required as well?), can we support DI in a language nicely enough without adding additional complexity to a language's module system to support similar capabilities?

Scott

Because someone has to

I'll put in the obligatory reference to Scalable Component Abstractions, the upshot of which is that with a suitably supple type system you don't even need the polymorphic construction you describe.

Makes the dependencies too visible

One obvious limitation that springs to mind with your approach is that now you've made the dependency a public part of the dependent class's type signature. Every place that takes a MyApplication will have to apply the type parameters or itself be type parametric so now your dependencies aren't encapsulated. One typical solution is to split the class into two levels, the parametric injected class and a non-parametric base one that you can pass around:

public interface IMyApplication
{
    void doSomething();
}

public class MyApplication : IMyApplication
{
    public void doSomething()
    {
        // ...
    }
}

Then you can pass it around as IMyApplication and avoid littering the dependencies through the codebase.

This works, but it is a bit cumbersome (especially in cases where you need to create your dependency with more than a default constructor). I'm not sure it buys you much over regular DI and/or factories. That being said, I have used this before and found it fit some problems OK.

Scala's type members

Scala's type members would allow you to hide those types.

class Dependent {
   type D <: Dependency
}

val d: Dependent = new Dependent { type D = ServiceImpl1 }

The type that you deal with is just "Dependent". You can use the type member "D" explicitly if you want, but you don't have to.

On the other hand, Scala doesn't support calling "new D()". That's a feature of C# that isn't really fundamental to the idea of parameterized types.

Haskell might have everything required to do this cleanly. Type classes would handle the "new D()" part and GHC's existential types could hide the type parameters. But like others have said, this seems like a less elegant technique than just passing factory methods or vtables into the constructor.