The point of having static typing is the ability to prove statically that your program is correct with regard of types (note: not completely correct in all senses). If you have a static type system throughout, you can detect type errors most of the time.
If you only have partial type information, you can only check the small pieces of a call graph where type info happens to be complete. But you have spent time and effort to specify type information for incomplete parts, where it can't help you but could give a false sense of security.
To express type information, you need a part of language which cannot be excessively simple. Soon you'll find out that info like int
is not enough; you'll want something like List<Pair<Int, String>>
, then parametric types, etc. It can be confusing enough even in the rather simple case of Java.
Then, you'll need to handle this information during translation phase and execution phase, because it's silly to only check for static errors; the user is going to expect that the type constraints always hold if specified at all. Dynamic languages are not too fast as they are, and such checks will slow the performance down even more. A static language can spend serious effort checking types because it only does that once; a dynamic language can't.
Now imagine adding and maintaining all of this just so that people sometimes optionally used these features, only detecting a small fraction of type errors. I don't think it's worth the effort.
The very point of dynamic languages is to have a very small and very malleable framework, within which you can easily do things that are much more involved when done in a static language: various forms of monkey-patching that are used for metaprogramming, mocking and testing, dynamic replacement of code, etc. Smalltalk and Lisp, both very dynamic, took it to such an extreme as to ship environment images instead of building from source. But when you want to ensure that particular data paths are type-safe, add assertions and write more unit tests.
Update from 2020: Some dynamic languages now support partial typing of sorts. Python allows type hints, to be used by external tools like mypy
. TypeScript allows mixing with type-oblivious JavaScript. Still, the points above mostly hold.
Are there any other objections? Are there any legitimate, real-world reasons why DI with a static/global service locator would be bad?
Ugh, yes.
Statics/globals are horrible. They assume that the runtime of your application is homogenous - all of your instances require all the same sort of instances all over. That is naive. They interfere with concurrency, since any state in any of these things is inherently shared state. They interfere with testing, since you can't effectively mock different instances running concurrently or even different instances down the callstack. And then there's the usual debugging issues with global state.
Hiding your problems is not fixing your problems. If you have something that everything needs, then you have widespread coupling. If you have objects that need a bunch of dependencies, then you have widespread coupling. If you have a huge object hierarchy that makes it difficult to pass things around where they need to go, then you probably have a poor design. DI does not fix these things! All you're doing is hiding it behind some magic that passes dependencies around. You're not removing the dependencies, just making them less visible and a runtime error rather than a compile time error.
Best Answer
The main benefit of a DI framework is that it moves construction into a different language (xml, json, whatever). This enforces not mixing construction code with behavior code. It's a poor programming team that needs that, but it works.
DI doesn't require a framework. Simply not mixing these responsibilities is enough. Construction also doesn't have to be done procedurally in main. You're entitled to use every feature of the language. Creational patterns have come a long way. It's when you mix use and construction that you find yourself hard coding dependencies with no way to override.
Good defaults are extremely easy to override as needed in a language with named parameters. This makes a much bigger impact to DI then dynamic typing. DI works well in dynamic languages. Even prototypical languages can benefit. That's because DI's is more then just mechanical. It actually makes code easier to read, if you're doing it right.
I recommend everyone learn how to do DI without a framework before trying to evaluate what any one framework provides. Some are useful even when you have the skill to live without them. Some are just something else trying to convince you to become dependent on it. Use with caution.