Why Functional Programming Matters (to me)
This is my response to being asked not to use functional programming techniques.
What I mean by “Functional Programming”
Different people mean different things by “Functional Programming”. Here’s what it means to me:
- Functions are first class, fundamental units of software, and proper functional programming languages allows passing functions as arguments, assigning them to variables, and composing them together to form new functions.
- Strong Static Typing prevents many common programming errors automatically.
- Together with sum types, strongly typed languages can automatically perform local totality checking, informing the programmer when a logical branch is not handled.
- Single assignment and segregation of mutable state: Mutable state is a major source of software errors. In most cases, its completely unnecessary. Immutable data types allow strong guarantees of thread safety, and leads to cleaner, more readable code.
Static vs. Dynamic Typing & Technical Debt
Statically typed languages have a [well deserved] reputation for being more difficult to get started with. When data types are decided at compile time, the compiler stops you from creating a nonsensical program. This does mean you need to apply more forethought to your code. Dynamically typed languages will happily allow you to run an incorrect program, and then throw a runtime exception when the nonsensical logic branch is reached. The question is: when do you want to grapple with the error in your thinking? At compile time, or runtime? Development or production?
The financial analogy is apt: would you prefer to finance your business by borrowing money, then slowly pay it back with interest? Or to pay as you go? There are appropriate uses of leveraged debt. Likewise, there is a appropriate place for dynamically typed languages: one-off programs and simple prototypes that need to be rapidly developed at the expense of quality and correctness.
Use the Best Available Tools
Functional Programming leads to simpler code that’s easy to refactor and maintain. When you need to change a datatype or function signature, the compiler can automatically find all the relevant code that needs to change. Some refactoring tools even allow you to automatically perform the changes. Its true that you can write bad code in any language, but that doesn’t mean that all languages are the same. Some languages make it hard to write good code, and others encourage good habits. The language you pick matters.
With the right choices, entire classes of common software problems can be solved. For example, null pointer dereferences can by virtually eliminated using Optional/Option/Maybe/Either data types to explicitly represent empty or error conditions.
Conversely, If you force engineers to use inferior tools, you can expect frustration and higher maintenance costs.
Complexity and Boredom
Writing software is a battle with complexity. We solve complicated problems that reflect a complicated world. But not all complexity is the result of modeling a complex problem domain. The distinction is between “Accidental” and “Essential” complexity.
Accidental complexity results from the arbitrary choices we make when approaching a problem. We might represent a collection using an array when a set or hash-map is more appropriate. Perhaps we’re using a language that requires manually memory allocation, when a garbage collected language would perform acceptably well. Accidental complexity can be addressed by choosing the right data structures, algorithms, and languages, if you know about them.
By contrast, “Essential” complexity is the result of grappling with the hard facts of the universe. Solving problems that emerge from the essential complexities of the world is exciting and fun. Solving problems caused by accidentally complex technical choices is tedious and boring.
Functional Programming is not a silver bullet. But it does eliminate several classes of accidental complexity that have plagued the software industry in recent decades.
Functional Programming is the Future
Traditional approaches to multi-threaded execution often turn into a nightmare of callbacks and deadlocks. However, using immutable data structures and work-stealing, many common operations like mapping can be done in parallel, with trivial changes to your existing (Functional!) code. Many aggregation & reduction operations can be done incrementally in a distributed fashion using monoidal data structures.
In some cases, significant speedups can be attained simply by switching from sequential arrays to a compatible parallel version of the same interface. The payback for Functional Programming is huge, and growing. Moore’s law is dead, and its not coming back. However, we’ve not nearly approached the physical limit of the number of cores we can pack into a single machine. The number of cores per CPU will probably continue to grow for decades. What’s more, massively distributed architectures are now available and commonplace, and the same techniques work there.
Embracing Functional Programming will help create a culture of learning. It will attract smarter, better skilled people, and allow us to reap the rewards of simpler, cleaner, more scalable code. Rejecting it will cost more in maintenance and higher turnover.