In my previous column, I discussed the power of Map/Reduce, both in terms of its common use as an engine for distributed analysis of Big Data and also as a fundamental concept, an insight into the power of the functional programming concepts of Map (which applies a function across a collection of elements) and Reduce (which summarizes or otherwise condenses a collection of elements).
As I’ve also discussed in my column, for two years Scala was my day-to-day programming language, and for much of that time I waited for the functional programming “light bulb” that would help me understand the zeal of functional programmers, who often seem unrealistically enthusiastic about their approach and offputtingly dismissive of any benefits to be had from object-oriented thinking.
I’m a stupid person. This is not news, but one of the ways my stupidity manifests is that it’s hard for me to recognize—until they’re no longer easily available—the best elements of a tool. It was only when I started programming in C++ that Smalltalk made sense to me. And it’s only been recently, when I returned to the land of more mainstream languages, that I can sense a functional light bulb flickering on.
Many discussions of functional programming argue that the key is the lack of reassignment to variables after they’ve been initialized (also known as immutability, referential transparency, etc.). And that’s certainly beneficial, but as I’ve argued in the past, the technique of preferring immutability to state is simply good programming and does not, I think, guide technical design or architecture. Admittedly, functional languages’ emphasis on programming without reassignment drives the developer toward separating control flow from data manipulation (and that’s a benefit), but it’s not transformative. It also drives the programmer toward recursion over iteration, a choice that is greatly approved of in certain circles but has never struck me as a hugely important design decision.
But there are two aspects of functional programming that I find are dramatically underused in day-to-day object-oriented programming. One is lazy evaluation. This is the ability of a functional language to pause a calculation, return an intermediate value, and then continue the calculation at some time in the future. Most modern languages have a “yield” keyword, and if you combine a yield with a recursive function, you get lazy evaluation-like behavior, including the ability to define very large—even infinite—sequences, and yet work with them efficiently. For instance, with lazy evaluation, you can define an infinite search of a tree-like data structure, but then very easily limit the depth to which you actually search.
Once I lost laziness as a day-to-day facility of my programming language, I found myself asking “Where can I yield?” in my designs. This has improved my designs because I can confidently delay the bookkeeping aspects of a search or traversal. These aspects can clutter code significantly since they often demand that your functions, in addition to doing their domain work, maintain a knowledge of “where in the calculation” they are.
The other question that I’ve begun to ask myself is “What would Map do?” This is my shorthand reminder to always think about how higher-order functions would apply to my classes. (I still think structuring code with classes is logical, efficient and teachable. Functional programming may have a better foundation in math, but OOP has a better foundation in people.) “Map” and “Reduce” are just two of several commonly written higher-order functions that are most familiar from collection classes. My jaw would drop if any programmer told me they didn’t prefer working with these types of functions to writing imperative loops. But the flickering light bulb in my head is not “Those are nice in collection classes,” but “A Map function would make me structure this class differently.”
Specifically, I think that always considering higher-order functions naturally guides you toward class structures that have two benefits: classes naturally prefer aggregation to inheritance, and individual classes seem to have the same level of abstraction in their contents.
For instance, in classic object orientation, the sentence “A Car is an object that has an Engine and four Wheels and some number of Doors” might guide you to a Car that has one Engine (a relatively abstract concept), a hard-coded number of Wheels (quite concrete), and an indeterminately-sized collection of Doors (a concept that may or may not even warrant a class to model). “What would Map do?” guides the design (I think) toward a Car object that aggregates traveling statistics (odometer and speedometer readings), concepts relating to power (the Engine), and physical components. That might not be a good design, but the important thing is that it’s a perspective that I don’t think would naturally arise from standard OOP processes.
I don’t always actually write higher-level functions for my classes. Although the question guided me in valuable ways, I’m actually not sure about using “What would Map do?” for a Car class. I suspect that instead of a single Map function, the answer might be several functions with names meaningful to the automotive domain (like MapTravelStatistics, MapPower and MapComponents). And then one higher-order function to rule them all? I don’t know; the light bulb is still flickering.
Larry O’Brien is a developer evangelist/advocate for Xamarin. Read his blog at www.knowing.net.