The "expression problem" or the "extensibility problem" is closely related to the Open-Closed Principle (OCP). OCP states that software should be open for extension but closed to modification. The expression problem observes that for the two fundamental dimensions found in programs, data types and operations, OCP is only achieved for one but not both.
Take, for example, an application involving vehicles. There two kinds of vehicles, automobiles and trucks. All vehicles can accelerate. It is easy to extend our application to include space ships.
The problem arises when we realize that vehicles should also be able to brake. To add a brake operation we will need to modify vehicle, automobile and truck; violating OCP.
We can attempt to approach things from a more operational point of view. Using something like the visitor pattern we can build our application to handle new operations without the need to modify existing source code. Here we simply add a new brake operation visitor.
But now the problem becomes how do we add a new vehicle to our application without violating OCP. It is not hard to add a truck vehicle but all of our existing operations must be aware of truck.
Mind you, we want to achieve everything discussed so far with type safety, and static type safety at that, because, as Bertrand Meyer would argue, run time is a little late to find out whether you have a landing gear. Incidentally, if you're wondering what the mysteries of life title is all about, it from this Meyer's article. I find his style amusing.
So how can we solve this expression problem and the goals of Open-Closed? Don't ask me, I'm just a guy with a blog who likes to use OmniGraffle. But from what I've been able to gather there's no easy, straightforward solution. We can use generics and "F-bounds" but the code then looks like
interface FooBar<C extends FooBar<C>> extends Bar<C> {...}
Quick, anybody knows what this means, because I sure have a hard time keeping track of recursive types. The Scala guys say you can solve the problem with abstract types, self types and mixins. But there seems to be a lot of scaffolding involved--lots of redefinition. Maybe it's not so bad if I really knew Scala beyond a cursory level. Some people argue these techniques only work if you have the foresight--you have to parameterized your design from the beginning; you have to declare types as abstract types from the start; etc. Multi-dispath/multi-methods can help, but I don't think any mainstream languages support those constructs.
In conclusion, I have no idea what I'm talking about. But I do find it interesting--and a bit sad--that such a seemingly fundamental problem, the expression problem, is pretty involved to solve. You would think by now some brilliant language designer would have figured things out in a simple, elegant way.
Comments