What do you think of when you hear the term "functional programming"? For many C# developers, functional programming is code written in other weird looking programming languages, by mathematical boffins who enjoy using lots of complicated abstract terminology like "monads" or "referential transparency".
Sadly, many explanations of functional programming can seem impenetrably obscure, leaving many of us "ordinary" enterprise programmers concluding that whatever benefits it may offer, the cost of learning it is too great. After all, we're not being paid to create the most elegant structure possible, we're paid to solve real-world business problems.
But over recent years, as I've slowly begun to familiarise myself with what "functional programming" is all about, I've discovered that a lot of its big ideas and core concepts aren't actually that complicated, and many of them can be applied even in languages like C# which admittedly is more of an "object-oriented" language than a "functional" one.
So, here's a quick list of eight "big ideas" of functional programming, each of which deserves a post of its own, but for now I'll try to give each one a quick explanation.
Big Idea 1: Declarative instead of Imperative Code
Declarative code tells the computer what you want to achieve, whereas imperative code goes into detail of how to achieve that. Of course the low-level implementation code does still need to exist somewhere, but by separating it out, your programs will become much more concise and readable.
For example, declarative drawing code might say "I want a green square of width 100 with a red circle of radius 40 in the middle". We don't really care about the details of how to work out all the parameters that need to be passed to the circle drawing method. That code can be handled by generic, reusable, lower-level functions.
Big Idea 2: Combining Functions
In functional programming, functions are the core building blocks, rather than classes. You work with lots of simple functions, which you combine together in various ways. This includes "composing" functions, where you make a new one out of two smaller ones, and "chaining" functions, where you make a "pipeline" passing the output from one function into the input of the next.
This style of programming often requires passing functions as parameters to other functions, which is something C# supports through delegates or "lambdas". If you adopt this style, your program becomes a series of transformations on data, each passing on to the next step.
Big Idea 3: Being Generic
Even if you're not into functional programming, you probably know it's a good idea to break out small pieces of functionality into their own methods, to make code easier to read and maintain, and also to promote reuse.
But functional programming seeks to take this concept as far as possible and actively looks for ways to make functions work with the most generic types possible. This way you end up with many small but powerful and reusable functions.
Big Idea 4: Being Lazy
Being lazy is about not calling a function unless you have to, and not calling it more times than you need to. It's surprising how much unnecessary work our programs often do. So this will often mean passing functions into other functions as parameters. That way they can be called only at the point we need to. This can give significant performance increases.
Big Idea 5: Eliminating Repeated Structure
try catch blocks? Always writing
foreach loops? Always writing checks for
null on an object before calling a member? These examples of repetitive boilerplate code are considered an unavoidable fact of life for a C# programmer, but a functional programmer sees these patterns, and tries to abstract them away into reusable functions. This is something that is not always straightforward with the C# language, but one example of it working is LINQ, which allows you to replace a lot of repetitive
if statements nested inside
foreach loops with a single query expression.
Big Idea 6: Eliminating Side Effects
"Side effects" are whenever your function modifies with external state, like writing to disk, receiving user input, displaying data on the screen, making a network request. Even throwing an exception or modifying a global variable is a side effect.
Obviously a program with no side effects would be pointless. But functional programming seeks to eliminate as many side effects as possible, and manage the ones that are essential, so that the majority of the program is implemented in terms of "pure" functions. These are functions guaranteed to return the same output given the same input. And this turns out to have a lot of benefits, not least in terms of testability, and confidence that your program will behave as expected.
Big Idea 7: Immutability
Perhaps one of the hardest ideas from functional programming for a C# programmer to stomach is the idea of immutable data structures. In other words, once you've created an object, you don't change its state. Instead you create a brand new object with the new state.
It does require a different way of thinking, and the C# language lacks some features that would make working with immutable data types less painful. But there are many benefits to immutability, including making multi-threaded code much simpler to write, and can make certain problem types much easier to implement (e.g. an undo feature). And if you are writing pure functions, then you are working with immutable data anyway, because changing any types passed in would be a side effect, so you have to return a new object.
Big Idea 8: Correctness
Functional programmers aim for the goal of "making invalid state impossible to express", and features of functional languages like F# such as Discriminated Unions can go a long way to helping you achieve this. If we construct our data structures and systems in such a way that it is not possible for them to get into an invalid state, then a whole category of hard to diagnose bugs simply go away.
Again, C# unfortunately lacks some features that would make this easier. To give a simple example, in C# a method that returns a
Customer could return
null - as a caller you can't be sure whether you need to handle that case or not. But in functional programming you must be explicit about whether a function can return a value or not, and a functional language will force the caller to handle both cases if the function might not return a
Functional C# with LINQ
I think that LINQ stands out as the best example of these big ideas. It promotes a declarative style, chaining together generic (and usually “pure”) functions, is lazily evaluated, eliminates many repetitive coding patterns, and promotes an immutable style by not modifying the underlying collections it operates on. So if you’re a fan of LINQ, you’re already well on your way to becoming a functional programmer.
Of course all of these big ideas deserve a lot more to be said about them, and there I’m sure several more could be added to the list (tell me what I’ve missed in the comments). But hopefully I’ve demonstrated that the big ideas of functional programming aren’t all that scary, and most of them can be applied quite straightforwardly in languages like C#.