Chapter 1. Introduction
If you’ve learned much programming before now—whether in C#, Microsoft Visual Basic, Python, or whatever—then chances are that what you learned was based around the programming paradigm that is currently the most dominant: object oriented. Object-oriented programming (OOP) has been around for quite a long time. The precise date is a matter of debate, but it was likely invented somewhere around the late ’50s or early ’60s.
Object-oriented coding is based around the idea of wrapping pieces of data—known as properties—and functionality into logical blocks of code called classes, which are used as a sort of template from which we instantiate objects. There’s a lot more to it: inheritance, polymorphism, virtual and abstract methods—all sorts of stuff like that.
This is, however, not an OOP book. In fact, if you are already experienced with OOP, you’ll probably get more from this book if you leave what you know already to one side.
In this book, I’ll be describing a style of programming that serves as an alternative to OOP: functional programming (FP). FP, despite gaining some mainstream recognition in the last few years, is actually as old, if not older, than OOP. FP is based on mathematic principles developed between the late 1800s and 1950s, and has been a feature of some programming languages since the 1960s. I’ll be showing you how to implement FP in C# without the necessity of learning a whole new programming language.
Before we get cracking with some code, I’d like to talk first about FP itself. What is it? Why should you be interested? When is it best used? These are all important questions.
What Is Functional Programming?
FP has a few basic concepts, many of which have fairly obscure names but are otherwise not terribly difficult to understand. I’ll try to lay them out here as simply as I can.
Is It a Language, an API, or What?
FP isn’t a language or a third-party plug-in library in NuGet; it’s a paradigm. What do I mean by that? Although more formal definitions of paradigms exist, I think of it as a style of programming. Just as a guitar might be used to play many, often wildly different, styles of music, some programming languages offer support for different styles of working.
FP is also as old as OOP, if not older. I’ll talk more about its origins in “Where Does Functional Programming Come From?”, but for now just be aware that it is nothing new, and the theory behind it predates not only OOP, but also the computing industry itself.
It’s also worth noting that you can combine paradigms, like mixing rock and jazz. Not only can they combine, but sometimes you can use the best features of each to produce a better end result.
Programming paradigms come in many flavors,1 but for the sake of simplicity, I’m going to talk about the two most common in modern programming:
- Imperative
-
This was the only type of programming paradigm for quite a long time. Procedural programming and OOP belong to this category. These styles of programming more directly instruct the executing environment of the steps that need to be executed in detail, i.e., which variable contains which intermediate steps and how the process is carried out step-by-step in minute detail. This is programming as it’s usually taught in school or at work.
- Declarative
-
In this programming paradigm, we’re less concerned with the precise details of how we accomplish our goal. Instead, the code more closely resembles a description of what is desired at the end of the process, and the details (such as the order of execution of the steps) are left more in the hands of the execution environment. This is the category FP belongs to. Structured Query Language (SQL) also belongs here, so in some ways FP more closely resembles SQL than OOP. When writing SQL statements, you aren’t concerned with the order of operations (it’s not really
SELECT
, thenWHERE
, thenORDER BY
), or with how exactly the data transformations are carried out in detail; you just write a script that effectively describes the desired output. These are some of the goals of functional C# as well, so those of you with a background working with Microsoft SQL Server or other relational databases might find some of the upcoming ideas easier to grasp than those who haven’t.
Many more paradigms besides these exist, but they’re well beyond the scope of this book. In fairness, most others are pretty obscure, so you’re unlikely to run into them anytime soon.
The Properties of Functional Programming
This section covers each of the properties of FP and what they really mean to a developer.
Immutability
If something can change, it can also be said to mutate, like a Teenage Mutant Ninja Turtle.2 Another way of saying that something can mutate is that it is mutable. If, on the other hand, something cannot change at all, it is immutable.
In programming, this refers to variables that have their value set upon being defined, and after that point, they may never be changed again. If a new value is required, a new variable should be created, based on the old one. This is how all variables are treated in functional code.
It’s a slightly different way of working compared to imperative code, but it ends up producing programs that more closely resemble the step-by-step work we do in math to reach a solution. This approach encourages good structure and more predictable—and hence more robust—code.
DateTime
and String
are both immutable data structures in .NET. You may think you’ve altered them, but behind the scenes every alteration creates a new item on the stack. This is why most new developers get the talk about concatenating strings in for
loops and why you should never, ever do it.
Higher-order functions
Higher-order functions are passed around as variables—either as local variables, parameters to a function, or return values from a function. The Func<T,TResult>
or Action<T>
delegate types are perfect examples.
In case you aren’t familiar with these delegates, this is how they work in brief. They’re both forms of functions stored as variables. They both take sets of generic types, which represent their parameters and return types, if any. The difference between Func
and Action
is that Action
doesn’t return any value—i.e., it’s a void
function that won’t contain a return
keyword. The last generic type listed in a Func
is its return type.
Consider these functions:
// Given parameters 10 and 20, this would output the following string:
// "10 + 20 = 30"
public
string
ComposeMessage
(
int
a
,
int
b
)
{
return
a
+
" + "
+
b
+
" = "
+
(
a
+
b
);
}
public
void
LogMessage
(
string
a
)
{
this
.
Logger
.
LogInfo
(
"message received: "
+
a
);
}
They can be rewritten as delegate types like this:
Func
<
int
,
int
,
string
>
ComposeMessage
=
(
a
,
b
)
=>
a
+
" + "
+
b
+
" = "
+
(
a
+
b
);
Action
<
string
>
LogMessage
=
a
=>
this
.
Logger
.
LogInfo
(
$
"message received: {a}"
);
These delegate types can be called exactly as if they were standard functions:
var
message
=
ComposeMessage
(
10
,
20
);
LogMessage
(
message
);
The big advantage of using these delegate types is that they’re stored in variables that can be passed around the codebase. They can be included as parameters to other functions or as return types. Used properly, they’re among the more powerful features of C#.
Using FP techniques, delegate types can be composed together to create larger, more complex functions from smaller, functional building blocks—like LEGO bricks being placed together to make a model Millennium Falcon or whatever you prefer. This is the real reason this paradigm is called functional programming: because we build our applications with functions, not, as the name suggests, that the code written in other paradigms doesn’t function. Why would anyone ever use those paradigms if they didn’t work?
In fact, here’s a rule of thumb for you: if there’s a question, FP’s answer will almost certainly be “functions, functions, and more functions.”
Note
Two kinds of callable code modules exist: functions and methods. Functions always return a value, but methods don’t. In C#, functions return data of some kind, whereas methods have a return type of void
. Because methods almost inevitably involve side effects, we should avoid any use of them in our code—except where unavoidable. Logging might be an example of method use that is not only unavoidable but also essential to good production code.
Expressions rather than statements
A couple of definitions are required here. Expressions are discrete units of code that evaluate to a value. What do I mean by that?
In their simplest form, these are expressions:
const
int
exp1
=
6
;
const
int
exp2
=
6
*
10
;
We can feed in values too, to form our expressions, so this is one as well:
public
int
addTen
(
int
x
)
=>
x
+
10
;
This next one does carry out an operation—i.e., determine whether or not a condition is true—but it ultimately simply returns a value of type bool
, so it’s still an expression:
public
bool
IsTen
(
int
x
)
=>
x
==
10
;
You can also consider ternary statements (the short form of an if
statement) to be expressions if they’re used purely for determining a value to return:
var
randomNumber
=
this
.
_rnd
.
Generate
();
var
message
=
randomNumber
==
10
?
"It was ten"
:
"it wasn't ten"
;
Another quick rule of thumb: if a line of code has a single equals sign, it’s likely to be an expression, because it’s assigning a value to something. That rule has some gray area. Calls to other functions could have all sorts of unforeseen consequences. It’s not a bad rule to keep in mind, though.
Statements, on the other hand, are pieces of code that don’t evaluate to data. These typically indicate an instruction to do something—either an instruction to the executing environment to change the order of execution via keywords like if
, where
, for
, and foreach
or calls to functions that don’t return anything—and by implication instead carry out some sort of operation. Here’s an example:
this
.
_thingDoer
.
GoDoSomething
();
A final rule of thumb is that if the code has no equals sign, it is definitely a statement.3
Expression-based programming
If it helps, think back to mathematics lessons from your school days. Remember those lines you used to have to write out to show your work when you were producing your final answer? Expression-based programming is like that.
Each line is a complete calculation and builds on one or more previous lines. By writing expression-based code, you’re leaving your work behind, set in stone while the function runs. Among other benefits, the code is easier to debug, because you can look back at all the previous values and know they’ve not been changed by a previous iteration of a loop or anything like that.
This approach might seem like an impossibility, almost like being asked to program with your arms tied behind your back. It’s entirely possible, though, and not even necessarily difficult. The tools have mostly been there for about a decade in C#, and plenty of more effective structures are available.
Here’s an example of what I mean:
public
decimal
CalculateHypotenuse
(
decimal
b
,
decimal
c
)
{
var
bSquared
=
b
*
b
;
var
cSquared
=
c
*
c
;
var
aSquared
=
bSquared
+
cSquared
;
var
a
=
Math
.
Sqrt
(
aSquared
);
return
a
;
}
Now, strictly speaking, you could write that out as one long line, but it wouldn’t look so nice and easy to read and understand, would it? You could also write this to save all the intermediate variables:
public
decimal
CalculateHypotenuse
(
decimal
b
,
decimal
c
)
{
var
returnValue
=
b
*
b
;
returnValue
+=
c
*
c
;
returnValue
=
Math
.
Sqrt
(
returnValue
);
return
returnValue
;
}
The issue here is that the code is a little harder to read without variable names, and all the intermediate values are lost—if there was a bug, we’d have to step through and examine returnValue
at each stage. In the expression-based solution, all the work is kept where it is.
After a little experience working in this manner, going back to the old way will actually seem odd and even a little awkward and clunky.
Referential transparency
Referential transparency is a scary-sounding name for a simple concept. In FP, pure functions have the following properties:
-
They make no changes to anything outside of the function. No state can be updated, no files stored, etc.
-
Given the same set of parameter values, they will always return the exact same result. No. Matter. What. Regardless of what state the system is in.
-
They don’t have any unexpected side effects. Exceptions being thrown is included in that.
The term referential transparency comes from the idea that given the same input, the same output always results, so in a calculation you can essentially swap the function call with the final value, given those inputs. Consider this example:
var
addTen
=
(
int
x
)
=>
x
+
10
;
var
twenty
=
addTen
(
10
);
The call to addTen()
with a parameter of 10
will always evaluate to 20
, with no exceptions. No possible side effects arise in a function this simple, either. Consequently, the reference to addTen(10)
could in principle be exchanged for a constant value of 20
with no side effects. This is referential transparency.
Here are some pure functions:
public
int
Add
(
int
a
,
int
b
)
=>
a
+
b
;
public
string
SayHello
(
string
name
)
=>
"Hello "
+
(
string
.
IsNullOrWhitespace
(
name
)
?
"I don't believe we've met. Would you like a Jelly Baby?"
:
name
);
No side effect can occur (I made sure a null check was included with the string), and nothing outside the function is altered; only a new value is generated and returned.
Here are impure versions of those same functions:
public
void
Add
(
int
a
)
=>
this
.
total
+=
a
;
// Alters state
public
string
SayHello
()
=>
"Hello "
+
this
.
Name
;
// Reads from state instead of a parameter value
Both of these cases include a reference to properties of the current class that are beyond the scope of the function itself. The Add()
function even modifies that state property. The SayHello()
function has no null check either. All these factors mean we cannot consider these functions to be pure.
How about these?
public
string
SayHello
()
=>
"Hello "
+
this
.
GetName
();
public
string
SayHello2
(
Customer
c
)
{
c
.
SaidHelloTo
=
true
;
return
"Hello "
+
(
c
?.
Name
??
"Unknown Person"
);
}
public
string
SayHello3
(
string
name
)
=>
DateTime
.
Now
+
" - Hello "
+
(
name
??
"Unknown Person"
);
None of these are likely to be pure.
SayHello()
relies on a function outside itself. I don’t actually know what GetName()
does.4 If it’s simply returning a constant, we can consider SayHello()
to be pure. If, on the other hand, it’s doing a lookup in a database table, then any missing data or lost network packets could result in errors being thrown (all examples of unexpected side effects). If a function had to be used for retrieving the name, I’d consider rewriting this with a Func<T,TResult>
delegate to inject the functionality safely into our SayHello()
function.
SayHello2()
modifies the object being passed in—a clear side effect from use of this function. Passing objects by reference and modifying them like this isn’t all that unusual a practice in OOP, but it’s absolutely not done in functional programming. I’d perhaps make this pure by separating out the update to the object properties and the processing of saying hello into two functions.
SayHello3()
uses DateTime.Now
, which returns a different value each and every time it’s used. This is the absolute opposite of a pure function. One easy way to fix this is by adding a DateTime
parameter to the function and passing in the value.
Referential transparency is one of the features that massively increases the testability of functional code. It does mean that other techniques have to be used to track state, and I’ll get into that in “Where Does Functional Programming Come From?”.
In addition, the amount of “purity” we can have in our application is limited, especially once we have to interact with the outside world, the user, or some third-party libraries that don’t follow the functional paradigm. In C#, we’re always going to have to make compromises here or there.
I usually like to wheel out a metaphor at this point. A shadow has two parts: the umbra and penumbra, as shown in Figure 1-1.5 The umbra is the solid, dark part of a shadow (most of the shadow, in fact). The penumbra is the gray, fuzzy circle around the outside, the part where shadow and not-shadow meet and one fades into the other. In C# applications, I imagine that the pure area of the codebase is the umbra, and the areas of compromise are the penumbra. My task is to maximize the pure area and to minimize as much as humanly possible the nonpure area.
Note
If you want a more formal definition of this architectural pattern, Gary Bernhardt has given talks calling it Functional Core, Imperative Shell.
Recursion
If you don’t understand this, see “Recursion”. Otherwise, see “Seriously, recursion”.
Seriously, recursion
Recursion has been around for as long as programming, pretty much. It’s a function that calls itself in order to effect an indefinite (but hopefully not infinite) loop. This should be familiar to anyone who has ever written code to traverse a folder structure or perhaps written an efficient sorting algorithm.
A recursive function typically comes in two parts:
- A condition
-
Used to determine whether the function should be called again or whether an end state has been reached (e.g., the value we’re trying to calculate has been found, there are no subfolders to explore, etc.)
- A return statement
-
Either returns a final value or references the same function again, depending on the outcome of the end-state condition
Here is a very simple recursive Add()
:
public
int
AddUntil
(
int
startValue
,
int
endValue
)
{
if
(
startValue
>=
endValue
)
return
startValue
;
else
return
AddUntil
(
startValue
+
1
,
endValue
);
}
Warning
Never do this particular example in production code. I’m keeping it simple for the purposes of explanation.
Silly though this example is, note that I never change the value of either parameter integer. Each call to the recursive function uses parameter values based on the ones it itself receives. This is another example of immutability: I’m not changing values in a variable; I’m making a call to a function using an expression based on the values received.
Recursion is one of the methods FP uses as an alternative to statements like while
and foreach
. Some performance issues occur in C#, however. Chapter 9, on indefinite loops, includes a more detailed discussion on the use of recursion, but for now just use it cautiously and stick with me. All will become clear…
Pattern matching
In C#, pattern matching is basically the use of switch
statements with “go-faster” stripes. F# takes the concept further, though. We’ve pretty much had pattern matching in C# for a few versions now. The switch
expressions in C# 8 gave us C# developers our own native implementation of this concept, and the Microsoft team has been enhancing it regularly.
With this switching, you can change the path of execution on the type of the object under examination as well as its properties. This process can be used to reduce a large set of nested if
statements like this:
public
int
NumberOfDays
(
int
month
,
bool
isLeapYear
)
{
if
(
month
==
2
)
{
if
(
isLeapYear
)
return
29
;
else
return
28
;
}
if
(
month
==
1
||
month
==
3
||
month
==
5
||
month
==
7
||
month
==
8
||
month
==
10
||
month
==
12
)
return
31
;
else
return
30
;
}
into a few fairly concise lines, like this:
public
int
NumberOfDays
(
int
month
,
bool
isLeapYear
)
=>
(
month
,
isLeapYear
)
switch
{
{
month
:
2
,
isLeapYear
:
true
}
=>
29
,
{
month
:
2
}
=>
28
,
{
month
:
1
or
3
or
5
or
7
or
8
or
10
or
12
}
=>
31
,
_
=>
30
};
Pattern matching is an incredible, powerful feature and one of my favorite things.6
The next couple of chapters contain many examples of this, so skip ahead if you’re interested in seeing more about what this is all about. Also, for those stuck using older versions of C#, there are ways of implementing this, and I’ll show a few tips in “Pattern Matching for Old Versions of C#”.
Stateless
Object-oriented code typically has a set of state objects, which represent a process—either real or virtual. These state objects are updated periodically to keep in sync with whatever it is they represent. You might have code like this, for example, which represents data about my all-time favorite TV series, Doctor Who:
public
class
DoctorWho
{
public
int
NumberOfStories
{
get
;
set
;
}
public
int
CurrentDoctor
{
get
;
set
;
}
public
string
CurrentDoctorActor
{
get
;
set
;
}
public
int
SeasonNumber
{
get
;
set
;
}
}
public
class
DoctorWhoRepository
{
private
DoctorWho
State
;
public
DoctorWhoRepository
(
DoctorWho
initialState
)
{
this
.
State
=
initialState
;
}
public
void
AddNewSeason
(
int
storiesInSeason
)
{
this
.
State
.
NumberOfStories
+=
storiesInSeason
;
this
.
State
.
SeasonNumber
++;
}
public
void
RegenerateDoctor
(
string
newActorName
)
{
this
.
State
.
CurrentDoctor
++;
this
.
State
.
CurrentDoctorActor
=
newActorName
;
}
}
Well, forget ever doing that again if you want to do FP. There is no concept of a central state object, or of modifying its properties, as in the preceding code sample.
Seriously? Feels like purest craziness, doesn’t it? Strictly, there is a state, but it’s more of an emergent property of the system.
Anyone who has ever worked with React-Redux has already been exposed to the functional approach to state (which was, in turn, inspired by the FP language Elm). In Redux, the application state is an immutable object, which isn’t updated, but instead a function is defined by the developer who takes the old state, a command, and any required parameters, and then returns a new state object based on the old one. This process became infinitely easier in C# with the introduction of record types in C# 9. I’ll talk more on that in “Record Types”. For now, though, a simple version of how one of the repository functions might be refactored to work functionally looks like this:
public
DoctorWho
RegenerateDoctor
(
DoctorWho
oldState
,
string
newActorName
)
{
return
new
DoctorWho
{
NumberOfStories
=
oldState
.
NumberOfStories
,
CurrentDoctor
=
oldState
.
CurrentDoctor
+
1
,
CurrentDoctorActor
=
newActorName
,
SeasonNumber
=
oldState
.
SeasonNumber
}
;
}
The big advantage of this approach is predictability. In C#, objects are passed by reference—meaning that if you change the object inside the function, it’s changed in the outside world as well! Therefore, if you’ve passed an object into a function as a parameter, you can’t be sure it’s unchanged, even though you didn’t specifically assign a new value to it.
All changes made to objects in FP are always done via deliberate assignment of the value, so there is never any ambiguity over whether a change has been made.
That’s enough about the properties of FP for now. Hopefully, you’ve got a good idea what it involves. Now I’m going to spend a bit of time being less intensely technical and consider FP from a wider perspective, as well as delve a little into FP’s origins.
Baking Cakes
Let’s look at a slightly higher level of description of the difference between the imperative and declarative paradigms. Here’s how each would make cupcakes.7
An Imperative Cake
This isn’t real C# code, just sort of .NET-themed pseudocode to give an impression of the imperative solution to this imaginary problem:
Oven
.
SetTemperatureInCentigrade
(
180
);
for
(
int
i
=
0
;
i
<
3
;
i
++)
{
bowl
.
AddEgg
();
bool
isEggBeaten
=
false
;
while
(!
isEggBeaten
)
{
Bowl
.
BeatContents
();
isEggBeaten
=
Bowl
.
IsStirred
();
}
}
for
(
int
i
==
0
;
i
<
12
;
i
++)
{
OvenTray
.
Add
(
paperCase
[
i
]);
OvenTray
.
AddToCase
(
bowl
.
TakeSpoonfullOfContents
());
}
Oven
.
Add
(
OvenTray
);
Thread
.
PauseMinutes
(
25
);
Oven
.
ExtractAll
();
For me, this represents typical convoluted imperative code: plenty of little short-lived variables cooked up to track state. The code is also very concerned with the precise order of things. It’s more like instructions given to a robot with no intelligence at all, needing everything spelled out.
A Declarative Cake
Here’s what an entirely imaginary bit of declarative code might look like to solve the same problem:
Oven
.
SetTemperatureInCentigrade
(
180
);
var
cakeBatter
=
EggBox
.
Take
(
3
)
.
Each
(
e
=>
Bowl
.
Add
(
e
)
.
Then
(
b
=>
b
.
While
(
x
=>
!
x
.
IsStirred
,
x
.
BeatContents
())
)
)
.
DivideInto
(
12
)
.
Each
(
cb
=>
OvenTray
.
Add
(
PaperCaseBox
.
Take
(
1
).
Add
(
cb
))
);
That might look odd and unusual for now, if you’re unfamiliar with FP, but over the course of this book, I’m going to explain how this all works, what the benefits are, and how you can implement all this yourself in C#.
What’s worth noting, though, is that there are no state-tracking variables, no if
or while
statements. I’m not even sure what the order of operations would necessarily be, and it doesn’t matter, because the system will work so that any necessary steps are completed at the point of need.
This is more like instructions for a slightly more intelligent robot, one that can think a little for itself. Instructions might sound something like “do this until such-and-such a state exists,” which in procedural code would exist by combining a while
loop and some state-tracking code lines.
Where Does Functional Programming Come From?
The first point I want to get out of the way is that despite what some people might think, FP is old. Really old (by computing standards, at least). It isn’t like the latest trendy JavaScript framework—here this year, old news the next. It predates all modern programming languages and even computing itself to some extent. FP has been around for longer than any of us and is likely to be around long after we’re all happily retired.
My slightly belabored point is that it’s worth investing your time and energy to learn and understand FP. Even if one day you find yourself no longer working in C#, most other languages support functional concepts to a greater or lesser degree (JavaScript does to an extent that most languages can only dream of), so these skills will remain relevant throughout the rest of your career.
A quick caveat before I continue: I’m not a mathematician. I love mathematics, which was one of my favorite subjects throughout all of my education, but there eventually comes a level of higher, theoretical mathematics that leaves even me with glazed-over eyes and a mild headache. That said, I’ll do my best to talk briefly about where exactly FP came from, which was, indeed, that very world of theoretical mathematics.
The first figure in the history of FP whom most people can name is usually Haskell Brooks Curry (1900–1982), an American mathematician who has no fewer than three programming languages named after him, as well as the functional concept of currying (you’ll learn more in Chapter 8). His work was on combinatory logic, a mathematical concept that involves writing out functions in the form of lambda (or arrow) expressions and then combining them to create more-complex logic. This is the fundamental basis of FP. Curry wasn’t the first to work on this, though; he was following on from papers and books written by these mathematical predecessors:
- Alonzo Church (1903–1955, American)
-
Church coined the term lambda expression, which we use in C# and other languages to this day.
- Moses Schönfinkel (1888–1942, Russian)
-
Schönfinkel wrote papers on combinatory logic that were a basis for Curry’s work.
- Friedrich Frege (1848–1925, German)
-
Arguably the first person to describe the concept we now know as currying. As important as it is to credit the correct people with discoveries, Freging doesn’t quite have the same ring.8
The first FP languages were the following:
- Information Processing Language (IPL)
-
Developed in 1956 by Allen Newell (1927–1992, American), Cliff Shaw (1922–1991, American), and Herbert Simon (1916–2001, American).
- LISt Processor (LISP)
-
Developed in 1958 by John McCarthy (1927–2011, American). I hear that LISP still has its fans to this day and is still in production use in some businesses. I’ve never seen any direct evidence of this myself, however.
Interestingly, neither of these languages are what you would call “pure” functional. Like C#, Java, and numerous other languages, they adopted something of a hybrid approach, unlike the modern “pure” functional languages, like Haskell and Elm.
I don’t want to dwell too long on the (admittedly, fascinating) history of FP, but it’s hopefully obvious from what I have shown that it has a long and illustrious pedigree.
Who Else Does Functional Programming?
As I’ve already said, FP has been around for a while, and it’s not just .NET developers who are showing an interest. Quite the opposite: many other languages have been offering functional paradigm support for a lot longer than .NET.
What do I mean by support? I mean that it offers the ability to implement code in the functional paradigm. This comes in roughly two flavors:
- Pure functional languages
-
Intended for the developer to write exclusively functional code. All variables are immutable, and the languages offer currying and higher-order functions out of the box. Some features of object orientation might be possible in these languages, but that’s very much a secondary concern to the teams behind them.
- Hybrid or multiparadigm languages
-
These two terms can be used entirely interchangeably. They describe programming languages that offer features enabling code to be written in two or more paradigms—often two or more at the same time. Supported paradigms are typically functional and object oriented. A perfect implementation of any supported paradigms may not be available. It’s not unusual for object orientation to be fully supported, but not to necessarily have all the features of the FP paradigm available to use.
Pure Functional Languages
Well over a dozen pure functional languages exist. Here is a brief look at the most popular ones in use today:
- Haskell
-
Used extensively in the banking industry, Haskell is often recommended as a great starting place for anyone wanting to really get to grips with FP. This may well be the case, but honestly, I don’t have the time or headspace to learn an entire programming language I never intend to use in my day job.
Note
If you’re really interested in becoming an expert in the functional paradigm before working with it in C#, by all means go ahead and seek out Haskell content. A frequent recommendation is Learn You a Haskell for Great Good! by Miran Lipovača (No Starch Press).9 I have never read this book myself, but friends of mine have and say it’s great.
- Elm
-
Elm seems to be gaining some traction these days, if for no other reason than the Elm system for performing updates in the UI has been picked up and implemented in quite a few other projects, including React.
- Elixir
-
This general-purpose programming language is based on the same virtual machine that Erlang runs on. It’s popular in industry and even has its own annual conferences.
- PureScript
-
PureScript compiles to JavaScript, so it can be used to create functional frontend code, as well as server-side code and desktop applications in isometric programming environments—i.e., those like Node.js that allow the same language to be used on the client and server side.
Is It Worth Learning a Pure Functional Language First?
For the time being at least, OOP is the dominant paradigm for the vast majority of the software development world, and the functional paradigm is something that has to be learned afterward. I don’t rule out that changing in the future, but for now at least, this is the situation we’re in.
I have heard people argue the point that, coming from OOP, it would be best to learn FP in its pure form first and then come back to apply that learning in C#. If that’s what you want to do, go for it. Have fun. I have no doubt that it’s a worthwhile endeavor.
To me, this perspective puts me in mind of those teachers we used to have here in the UK who insisted that children should learn Latin because, as the root of many European languages, knowledge of Latin can easily be transferred to French, Italian, Spanish, and so on.
I disagree with this somewhat.10 Unlike Latin, pure functional languages aren’t necessarily difficult, though they are very unlike object-oriented development. In fact, FP has fewer concepts to learn compared to OOP. This said, those who have spent their careers heavily involved in OOP will likely find it harder to adjust.
Latin and pure functional languages are similar, though, in that they represent a purer, ancestral form. They are both of only limited value outside of a small number of specialist interests.
Learning Latin is also almost entirely useless unless you’re interested in subjects such as law, classical literature, or ancient history. It’s far more useful to learn modern French or Italian. They’re easier languages to learn by far, and you can use them now to visit lovely places and talk to the nice people who live there. There are some great French-language comics from Belgium too. Check ’em out. I’ll wait.
In the same way, few places will ever actually use pure functional languages in production. You’d be spending a lot of time having to make a complete shift in the way you work and end up learning a language you’ll probably never use outside of your own hobby code. I’ve been doing this job for a long time and have never encountered a company using anything more progressive in production than C#.
The lovely thing about C# is that is supports both object-oriented and functional code, so you can shift between them as you please. Use as many features from one paradigm or the other as you like without any penalty. The paradigms can sit fairly comfortably alongside each other in the same codebase, so it’s an easy environment to transition from pure object oriented to functional at a pace that suits you, or vice versa. Mixing paradigms like this isn’t possible in a pure functional language, even if a lot of functional features aren’t possible in C#.
What About F#? Should I Be Learning F#?
What about F#? That’s probably the most common question I get asked. It’s not a pure functional language, but the needle is far closer to being a pure implementation of the paradigm than C#. It has a wide variety of functional features straight out of the box, as well as being easy to code in and producing applications that give a high level of performance in a production environment—why not use that?
I always like to check the available exits in the room before I answer this question. F# has a passionate user base, and they are probably all much smarter folks than me.11 But...
It’s not that F# isn’t easy to learn. It is, from what I’ve seen, and most likely it’s easier to learn than C# if you’re entirely new to programming.
It’s not that F# won’t bring business benefits, because I honestly believe it will.
It’s not that F# can’t do absolutely everything any other language can do. It most certainly can. I’ve seen some impressive talks on how to make full-stack F# web applications.
Whether to learn F# is a professional decision. It isn’t hard to find C# developers, at least in any country I’ve ever visited. If I were to put the names of every attendee of a big developers’ conference in a hat and draw one at random, there’s a better than even chance it would be someone who can write C# professionally. If a team decides to invest in a C# codebase, it’s not going to be much of a struggle to keep the team populated with engineers who will be able to keep the code well maintained and the business relatively content.
Developers who know F#, on the other hand, are relatively rare. I don’t know many. By adding F# into your codebase, you may be putting a dependency on the team to ensure that you always have enough people available who know it, or else take a risk that some areas of the code will be hard to maintain, because few people know how.
I should note that the risk isn’t as high as introducing an entirely new technology, like, say, Node.js. F# is still a .NET language and compiles to the same Intermediate Language (IL). You can even easily reference F# projects from C# projects in the same solution. F# syntax would still be entirely unfamiliar to the majority of .NET developers, however.
It’s my firm wish that this changes as time goes on. I’ve liked very much what I’ve seen of F#, and I’d love to do more of it. If my boss told me that a business decision had been made to adopt F#, I’d be the first to cheer!
The fact is, though, that scenario isn’t very likely at present. Who knows what the future will bring. Maybe a future edition of this book will have to be heavily rewritten to accommodate all the love for F# that’s suddenly sprung up, but for now I can’t see that on the near horizon.
My recommendation is to try this book first. If you like what you see, F# might be the next place you go on your functional journey.
Multiparadigm Languages
It can probably be argued that all languages besides the pure functional languages are some form of hybrid. In other words, at least some aspects of the functional paradigm can be implemented. That’s likely true, but I’m just going to look briefly at a few where FP can be implemented entirely, or mostly, and as a feature provided explicitly by the team behind it:
- JavaScript
-
JavaScript is, of course, almost the Wild West of programming languages in the way that nearly anything can be done with it, and it does FP very well—arguably better than it does object orientation. Have a look for JavaScript: The Good Parts by Douglas Crockford (O’Reilly) and some of his online lectures (for example, “JavaScript: The Good Parts”) if you want insight into how to use JavaScript functionally and properly.
- Python
-
Python has rapidly become a favorite programming language for the open source community, just over the last few years. It surprised me to find out it’s been around since the late ’80s! Python supports higher-order functions and has a few libraries available, such as itertools and functools, to allow further functional features to be implemented.
- Java
-
The Java platform has the same level of support for functional features as .NET. Furthermore, spin-off projects such as Scala, Clojure, and Kotlin offer far more functional features than the Java language itself does.
- F#
-
As I’ve discussed at length previously, F# is .NET’s more purely functional-style language. It’s also possible to have interoperability between C# and F# libraries, so you can have projects that utilize all the best features of both.
- C#
-
Microsoft has slowly been adding support for FP ever since somewhere near the beginning. Arguably, the introduction of delegate covariance and anonymous methods in C# 2.0 all the way back in 2005 could be considered the very first item to support the functional paradigm. Things didn’t really get going properly until the following year, when C# 3.0 introduced what I consider one of the most transformative features ever added to C#: LINQ.
LINQ is deeply rooted in the functional paradigm and is one of our best tools for getting started writing functional-style code in C# (have a look at Chapter 2 for a deeper discussion). In fact, it’s a stated goal of the C# team that each version of C# released should contain further support for FP than the one before it. A number of factors are driving this decision, but among them is F#, which often requests new functional features from the .NET runtime folks that C# ends up benefiting from too.
The Benefits of Functional Programming
I hope that you picked up this book because you’re already sold on FP and want to get started right away. This section might be useful for team discussions about whether to use it at work.
Concise
While not a feature of FP, my favorite of the many benefits is just how concise and elegant it looks, compared to object-oriented or imperative code.
Other styles of code are much more concerned with the low-level details of how to do something, to the point that sometimes an awful lot of code-staring is needed just to work out what that something even is. Functional programming is oriented more toward describing what is needed. The details of precisely which variables are updated, how and when, to achieve that goal are less of our concern.
Some developers I’ve spoken to about this have disliked the idea of being less involved with the lower levels of data processing, but I’m personally happier to let the execution environment take care of that. Then I have one thing fewer that I need to be concerned with.
It feels like a minor thing, but I honestly love the concision of functional code compared to the imperative alternatives. The job of a developer is a difficult one,12 and we often inherit complex codebases that we need to get to grips with quickly. The longer and harder it is for you to work out what a function actually does, the more money the business is losing by paying you to do that, rather than writing new code. Functional code often reads in a way that describes—in something approaching natural language—what is being accomplished. It also makes it easier to find bugs, which again saves time and money for the business.
Testable
One thing a lot of people describe as their favorite feature of FP is how incredibly testable it is. It really is. If your codebase isn’t testable to something close to 100%, there’s a chance you didn’t follow the paradigm correctly.
Test-driven development (TDD) and behavior-driven development (BDD) are important professional practices. These programming techniques involve writing automated unit tests for the production code first, and then writing the real code required to allow the test to pass. This approach tends to result in better-designed, more robust code. FP enables these practices neatly. This, in turn, results in a better codebase and fewer bugs in production.
Robust
It’s also not just the testability that results in a more robust codebase. FP comprises structures that actively prevent errors from occurring in the first place.
Alternatively, these structures prevent any unexpected behavior further on, making it easier to report the issue accurately. There is no concept of null in FP. That alone saves an incredible number of possible errors, as well as reducing the number of automated tests that need to be written.
Predictable
Functional code starts at the beginning of the code block and works its way to the end—exclusively in order. That’s something you can’t say of procedural code, with its loops and branching if
statements. FP has only a single, easy-to-follow flow of code.
When done properly, FP doesn’t even have any try
/catch
blocks, which I’ve often found to be some of the worst offenders when it comes to code with an unpredictable order of operations. If the try
isn’t small in scope and tightly coupled to the catch
, sometimes it can be the code equivalent of throwing a rock blindly up into the air. Who knows where it’ll land and who or what might catch it? Who can say what unexpected behavior might arise from such a break in the flow of the program?
Improperly designed try
/catch
blocks have been the cause of many instances of unexpected behavior in production that I’ve observed over my career, and it’s a problem that simply doesn’t exist in the functional paradigm. Improper error handling is still possible in functional code, but the very nature of FP discourages it.
Better Support for Concurrency
Two recent developments in the world of software development have become important in the last few years:
- Containerization
-
Provided by products such as Docker and Kubernetes, among others, containerization is the idea that instead of running on a traditional server—virtual or otherwise—the application runs on something like a mini virtual machine (VM), which is generated by a script at deployment time. It isn’t quite the same (no hardware emulation occurs), but from a user perspective, the result feels roughly the same. It solves the “it worked on my machine” problem that is sadly all too familiar to many developers. Many companies have software infrastructure that stacks up many instances of the same application in an array of containers, all processing the same source of input—whether that be a queue, user requests, or whatever. The environment that hosts them can even be configured to scale up or down the number of active containers, depending on demand.
- Serverless
-
This option might be familiar to .NET developers as Azure Functions or Amazon Web Services (AWS) Lambda. This is code that isn’t deployed to a traditional web server, such as Internet Information Services (IIS), but rather as a single function that exists in isolation out on a cloud-hosting environment. This approach allows the same sorts of automatic scaling as is possible with containers, but also micro-level optimizations: more money can be spent on more critical functions, and less money on functions requiring more time to render the output.
Both of these technologies use concurrent processing a great deal (i.e., multiple instances of the same functionality working at the same time on the same input source). It’s like .NET’s async features but applied to a much larger scope.
The problem with any sort of asynchronous operations tends to occur with shared resources, whether that’s in-memory state or a literal shared physical or software-based external resource. FP operates without state, so no state can be shared among threads, containers, or serverless functions.
When implemented correctly, the functional paradigm makes it much easier to implement these much-in-demand technological features, but without giving rise to any unexpected behavior in production.
Reduced Code Noise
Audio processing uses a concept called the signal-to-noise ratio. This is a measure of the clarity of a recording, based on the ratio of the volume level of the signal (the thing you want to listen to) to the noise (any hiss, crackle, or rumble in the background).
In coding, the signal is the business logic of a code block—the goal it is actually trying to accomplish. The signal is the what of the code.
The noise is all the boilerplate code that must be written to accomplish the goal. The noise includes the for
loop definition, if
statements, that sort of thing.
Compared to procedural code, neat, concise FP has significantly less boilerplate and so has a much better signal-to-noise ratio. This isn’t just a benefit to developers. Robust, easier-to-maintain codebases means the business needs to spend less money on maintenance and enhancements.
The Best Places to Use Functional Programming
FP can do absolutely anything that any other paradigm can, but in certain areas it’s strongest and most beneficial—and in other areas it might be necessary to compromise and incorporate some object-orientation features, or slightly bend the rules of the functional paradigm. In .NET, at least, compromises must be made because base classes and add-on libraries tend to be written following the object-oriented paradigm. This compromise doesn’t apply to pure functional languages.
FP is good when the code has a high degree of predictability—for example, data processing modules, or functions that convert data from one form to another. Business logic classes that handle data from the user or database and then pass it on to be rendered elsewhere are another example. Stuff like that.
The stateless nature of FP makes it a great enabler of concurrent systems—like heavily asynchronous codebases, or places where several processors are listening concurrently to the same input queue. When no shared state exists, it’s just about impossible to get resource contention issues. If your team is looking into using serverless applications, such as Azure Functions, then FP enables that nicely for most of the same reasons.
FP is worth considering for highly business-critical systems because the paradigm facilitates producing code that’s less error prone and more robust than applications coded with the object-oriented paradigm. If it’s incredibly important that the system should stay up, and not have a crash and burn (i.e., terminate unexpectedly) in the event of an unhandled exception or invalid input, then FP might be the best choice.
Where You Should Consider Using Other Paradigms
You don’t ever have to consider using other paradigms, of course. Functional can do anything, but looking around for other paradigms might be worthwhile in a few areas—purely in a C# context. And it’s also worth mentioning again that C# is a hybrid language, so many paradigms can quite happily sit side by side, next to each other, depending on the needs of the developer. I know which I prefer, of course!
Interactions with external entities is one area for consideration: for example, I/O, user input, third-party applications, and web APIs. There’s no way to make those pure functions (i.e., without side effects), so compromise is necessary. The same goes for third-party modules imported from NuGet packages. Even a few older Microsoft libraries are simply impossible to work with functionally. This is still true in .NET Core. Have a look at the SmtpClient
or MailMessage
classes in .NET if you want to see a concrete example.
In the C# world, if performance is your project’s only, overwhelming concern—trumping all others, even readability and modularity—then following the functional paradigm might not be the best idea. Nothing is inherently poor in the performance of functional C# code, but it’s not necessarily going to be the most utterly high-performing solution either.
I would argue that the benefits of FP far outweigh any minor loss of performance. These days, chucking a bit more hardware (virtual or physical, as appropriate) at the app is usually easy and is likely to be an order of magnitude cheaper than the cost of additional developer time that would otherwise be required to develop, test, debug, and maintain a codebase written in imperative-style code. This changes if, for example, you’re working on code to be placed on a mobile device, where performance is critical because memory is limited and can’t be updated.
How Far Can We Take This?
Unfortunately, it simply isn’t possible to implement the entirety of the functional paradigm in C#. There are all sorts of reasons for that, including the need for backward compatibility in the language and limitations imposed on what remains a strongly typed language.
The intention of this book isn’t to show you how all of it can be done, but rather to show the boundaries between what is and isn’t possible. I’ll also be discussing what’s practical, especially with an eye to those of you maintaining a production codebase. This is ultimately a practical, pragmatic guide to functional coding styles.
Monads Actually, Don’t Worry About This Yet
Monads are often thought to be the functional horror story. Look on Wikipedia for definitions, and you’ll be presented with a strange letter soup containing Fs, Gs, arrows, and more brackets than you’ll find under the shelves of your local library. Even now, I find these formal definitions utterly illegible. At the end of the day, I’m an engineer, not a mathematician.
Douglas Crockford once said that the curse of the monad is that the moment you gain the ability to understand it, you lose the ability to explain it. So I won’t. Monads might make their presence known somewhere in this book, however—especially at unlikely times.
Don’t worry; it’ll be fine. We’ll work though it all together. Trust me…
Summary
In this first exciting installment of Functional Programming with C#, our mighty, awe-inspiring hero—you—bravely learned just what exactly FP is and why it’s worth learning. You received an initial, brief introduction to the important features of the functional paradigm:
-
Immutability
-
Higher-order functions
-
Preference for expressions over statements
-
Referential transparency
-
Recursion
-
Pattern matching
-
Stateless
You read about the areas FP is best used in and the relative merits of using the paradigm in its pure form. You also looked at the many benefits of writing applications by using the functional paradigm.
In the next thrilling episode, you’ll learn what you can do in C# right here, right now. No new third-party libraries or Microsoft Visual Studio extension is required—just some honest-to-goodness out-of-the-box C# and a little ingenuity.
Turn the page to hear all about it. Same .NET time. Same .NET channel.13
1 Including vanilla and my personal favorite, banana.
2 They were “hero turtles” when I was growing up in the United Kingdom in the ’90s. I think the TV folks were trying to avoid the violent connotations of the word “ninja.” They nevertheless still let us see our heroes regularly use sharp, bladed implements on their villains.
3 Credit must be given to functional programming supremo Mark Seemann for giving me these handy rules.
4 Because I made it up for this example.
5 OK, artistic folks, I know there are actually about 12, but that’s more than I need for this metaphor to work.
6 For some reason, Julie Andrews won’t return my calls to discuss an updated .NET version of her famous song.
7 With a little creative liberty taken.
8 For example, “I can’t get this Freging code to work!”
9 Available to read online for free. Tell ’em I sent you.
10 Although I am learning Latin. Insipiens sum. Huiusmodi res est ioci facio.
11 Especially F# guru Ian Russell, who helped with the F# content in this book. Thanks, Ian!
12 At least that’s what we tell our managers.
13 Or book, if we’re being picky.
Get Functional Programming with C# now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.