The basic idea of dependency injection is this:
Here is some code that is not using dependency injection:
# Not using dependency injection (not Delight code) class HttpProxy: static HttpProxy singleton string[string] settings HttpProxy getSingleton(): if singleton is null: singleton = new HttpProxy() return singleton class Foo: void doSomething(): HttpProxy proxy = HttpProxy.getSingleton() proxy.get(...) class Main: void main(): Foo foo = new Foo() foo.doSomething()
The idea here is to simplify the API of Foo. The user doesn't need to tell foo how to find the HTTP proxy object with the proxy configuration; it finds it itself.
The problems with this are:
In dependency injection, the proxy must be passed explicitly:
# Using dependency injection class HttpProxy: string[string] settings class Foo: in HttpProxy proxy void doSomething(): proxy.get(...) class Main: void main(): HttpProxy proxy = new HttpProxy() Foo foo = new Foo(proxy) foo.doSomething()
Now it's clear that Foo uses an HTTP proxy, and we can create different objects with different settings.
Essentially, the Main class becomes a configuration file, instantiating, configuring and connecting together the components used in the program.
The Java Spring framework uses this style extensively to great effect (it refers to it as inversion of control).
Delight is unusual in that it enforces this style, rather than simply permitting it (as other languages do), by removing global state from the language. The first example isn't valid Delight code, due to the static class variable.
I did this because I have found that dependency injection works very well when used everywhere, but connecting dependency injected code with non-dependency injected code is painful. Delight is an experiment to see what happens when the whole language and standard library uses this method.
Also, it can eliminate some possibilities when trying to debug a program if you know that it can't be using global state, rather than thinking that it probably doesn't.
If you haven't used this style before, you might be tempted to create a huge GlobalState object in Main and pass it around everywhere. Doing that will lose a lot of the benefits.
Another trap is changing your library API to accept all the arguments it needs to build some object. For example, say you have:
# Not delight code class WebBrowser: void browse(): HttpProxy proxy = HttpProxy.getSingleton() ...
Now you can't call getSingleton, there is a temptation to do this:
# Bad style class WebBrowser: in Network network in Configuration config void browse(): HttpProxy proxy = new HttpProxy(network, config) ...
After all, you need a proxy, and to make a proxy you need a network and a configuration, so you should take a network and a configuration as inputs, right?
The correct solution is probably more like this:
class WebBrowser: in HttpProxy proxy void browse(): ...
As far as possible, try to avoid constructing things that you could have taken as inputs. This way: