The Inversion of Control (IoC) design pattern, functional programming, and Domain Specific Languages are all different faces of the same underlying concept and can be used interchangeably. Recognizing this should make it easier to understand each one and when they should be used within your own code.
IoC simply refers to a library passing control to it’s caller. A typical object-oriented implementation would be a function which takes an object that implements ISomething where ISomething has a single method that the function then calls. Those familiar with the visitor pattern should recognize that the visitor pattern is just a specialization of IoC where the callers code is applied to each object in a collection (actually any structure, it doesn’t *have* to be a collection).
I think the tie between IoC and functional programming should be pretty obvious at this point. If your language supports first class functions and closures, then creating an interface with a single method is just extra work when you can simply pass a function as a parameter. In fact, since a closure will close over lexical variables to maintain their “state”, both types of IoC implementation (Interfaces and Functions) are exactly equivelant in terms of power. I believe that using a simple function is more readable though, and it’s certainly more convienent for the caller to not have to create a class that implements ISomething.
The tie between DSLs and IoC is more abstract and requires exploration into the problems being solved. IoC is a useful design when the problem is not completely specified. If you are writing a library and the only thing that will vary in how it’s used is the inputs, IoC is just extra typing with no real benefit. If your problem is only partially specified on the other hand, then IoC allows you to implement the bits of the solution that you can infer from the specs, and leave the rest up to the caller. This is why very generic libraries tend to use IoC a lot. A DSL typically provides the users with a base set of combinators (functions that create functions out of other functions) to build up functions that perform a specific type of task in a very high level manner. A DSL for writing parsers should look very close to BNF so that a grammar can be encoded in the host language with as few implementation specific details as possible. How to do this with combinators is an interesting topic itself that deserves it’s own post and maybe I will get motivated and write one some day. A more intuitive example is configuration files, any time you have a configuration file that can change the flow of a program you are creating a mini-language and you are passing control to the end-user (ie.. IoC).
So great, they are the same thing conceptually, what do we gain from this? Well we can transfer the knowledge we have from one paradigm (like OO) to another (like functional) and vice versa. More concretely, by recognizing that they are all ways to partially specify a solution, we now have a good solid concrete criteria for deciding when one of these types of implementation are necessary.
On a side note, this should help highlight the difference between a pattern and an implementation. Interfaces/Functions/Monads are ways to implement the IoC pattern. Creating a library to “handle IoC” is confusing the two, and if you find yourself writing or consuming one of these libraries, then some reflection on how your handling the task at hand would probably do some good.
January 22, 2009 at 7:15 pm
Pretty much true, with one caveat:
You wrote this: “If your language supports first class functions and closures, then creating an interface with a single method is just extra work when you can simply pass a function as a parameter.”
Which points to the fact that IoC works a lot like using first-class functions.
IoC is a superset of this behavior in exactly the same way that OOP is a superset of non-OOP imperative programming specifically because that ISomething needn’t be a single object, or have a single function that is changed. It can be anything – an interface, a class, a function, a collection, and the way that it is transformed can be many and varied.
I think you’ve hit the nail on the head with DSL usage of combinators. That’s exactly how IoCs are used. Typically, common tasks such as logging, database connection tracking, and caching are shoved into the IoC common part, and not even *touched* by the standard codebase. This would obviously be done the same if you used a domain specific language.
But there’s still a bit extra with IoC generally – there’s a microkernel. So unlike just a domain specific language, the produced code comes not just from the combination of one set of functions and another, but also from the state of the other objects in the microkernel during runtime (though if you consider doing things with that part of the DSL, then maybe even that is not really covered).
More important, compared to functional design or DSLs, you don’t need a runtime compiler or first class functions in order to use an IoC container. They work almost everywhere.
January 22, 2009 at 8:04 pm
Rusty,
Correct me if I’m wrong but I think your saying that IoC is actually more powerful because the logic being specified by the consumer is not restricted to a function. It could be a collection of functions, or even an object where logic is created based on it’s structure. I have two comments then. 1) If your function takes an object with a set of properties and then your IoC container then executes different logic based on the value of the properties, then I would say you are in fact encoding another language in your host language and are accepting a function in that new language. 2) If we generalize IoC to anything that changes it’s logic based on the parameters, then in effect you could call almost any function that takes parameters IoC and the term loses meaning.
Item #2 makes me wonder how far we can take this generalization, and if IoC could possibly even be used as a tool to reason about arbitrary functions.