We need to learn how to effectively use multi-paradigm languages that support functional, object oriented, and procedural paradigms.
The programming world used to be split into functional languages, object-oriented languages, and everything else (mostly procedural languages). One “was” a functional programmer (at least as a hobby) writing Lisp, Haskell, or Erlang; or one “was” an OO programmer (at least professionally), writing code in Java or C++. (One never called oneself a “procedural programmer”; when these names escaped from academia in the 1990s, calling yourself a “procedural programmer” would be akin to wearing wide ties and bell-bottom jeans.)
Why do we still see holy wars between advocates of functional and object-oriented programming? That strikes me as a huge missed opportunity. What might “multi-paradigm programming” mean? What would it mean to reject purity and use whatever set of features provide the best solution in any given context? Most significant software is substantial enough that it certainly has components where an object-oriented paradigm makes more sense, and components where a functional paradigm is superior. For example, look at a “functional” feature like recursion. There are certainly algorithms that make much more sense recursively (Towers of Hanoi, or printing a sorted binary tree in order); there are algorithms where it doesn’t make much of a difference whether you use loops or recursion (whenever tail recursion optimizations will work); and there are certainly cases where recursion will be slow and memory-hungry. How many programmers know which solution is best in any situation?
These are the sort of questions we need to start asking. Design patterns have been associated with object-oriented programming from the beginning. What kinds of design patterns make sense in a multi-paradigm world? Remember that design patterns aren’t “invented”; they’re observed, they’re solutions to problems that show up again and again, and that should become part of your repertoire. It’s unfortunate that functional programmers tend not to talk about design patterns; when you realize that patterns are observed solutions, statements like “patterns aren’t needed in functional languages” cease to make sense. Functional programmers certainly solve problems, and certainly see the same solutions show up repeatedly. We shouldn’t expect those problems and solutions to be the same problems and solutions that OO programmers observe. What patterns yield the best of both paradigms? What patterns might help to determine which approach is most appropriate in a given situation?
Programming languages represent ways of thinking about problems. Over the years, the paradigms have multiplied, along with the problems we’re interested in solving. We now talk about event-driven programming, and many software systems are event-driven, at least on the front end. Metaprogramming was popularized by JUnit, the first widely used tool to rely on this feature that’s more often associated with functional languages; since then, several drastically different versions of metaprogramming have made new things possible in Java, Ruby, and other languages.
We’ve never really addressed the problem of how to make these paradigms play well together; so far, languages that support multiple paradigms have left it to the programmers to figure out how to use them. But simply mixing paradigms ad hoc probably isn’t the ideal way to build large systems–and we’re now building software at scales and speeds that were hard to imagine only a few years ago. Our tools have improved; now we need to learn how to use them well. And that will inevitably involve blending paradigms that we’ve long viewed as distinct, or even in conflict.
Thanks to Kevlin Henney for ideas and suggestions!