Functional Programming
- Understand what Functional Programming is and why it’s useful
- Learn the common Functional Programming techniques in C#
- Understand the LINQ ecosystem
Functional Programming is a style of programming in the same way as Object Oriented Programming is a style of programming. Writing great functional programs requires a slight mindset shift compared to the procedural (step-by-step) programming approach that comes naturally to most people, but it’s a powerful concept and one that you will already have used in some of the C# coding you have done to date.
What is Functional Programming?
Functional Programming is a programming style where you think primarily in terms of functions, in the mathematical sense of the word.
A mathematical function is something that takes some inputs, and produces an output. Importantly that’s all it does – there are no side effects such as reading data from a web page, writing text to the console or modifying some shared data structure. In the absence of such side effects, the only thing that can influence the function’s behaviour is its inputs – so given a particular combination of inputs, we will always get back precisely the same outputs. A function that has these properties is called a pure function.
Functional programming is all about using pure functions as the building blocks of a program. Consider the following procedural (non-functional) piece of code:
static void Main()
{
int[] input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
var list = new List<int>();
foreach (int i in input)
{
if (i % 2 == 0)
{
list.Add(i);
}
}
var output = list.ToArray();
}
This code takes an input array of integers, and produces an output array containing all the even numbers.
We call this code “procedural” because it defines a procedure for the computer to follow, step by step, to produce the
result. We are talking to the computer at a detail level, specifying precisely which numbers to move around. We are also
creating data structures that are modified as the program executes (the list
).
Consider the functional alternative, which is to perform all this using pure functions:
static void Main()
{
int[] input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
var output = input.Where(i => i % 2 == 0).ToArray();
}
This version is shorter. But that is not its main selling point. The real benefit is that we’re talking at a much more abstract level about what we want the program to achieve, rather than what we want it to do step by step. You can read the second line of code fairly directly:
- Take
input
- Apply a filter to it
- The filter is “number is even”
- Convert the result to an array
Deep inside the computer’s CPU it will still be doing pretty much the same operations as the previous version, but we
don’t need to know that – it’s a level of detail we don’t care about and it might not even be true. In the more general case where we don’t know the concrete type of input
(we might
define it just as IEnumerable<int>
, for instance), it may be a data structure that intrinsically supports parallel
programming and is in fact doing the filtering on a multitude of different threads. We don’t care, because we’re talking
at a much higher level about what we want to achieve (filtering, via the Where
method), rather than how we want to
achieve it (a foreach
loop, or perhaps some cunning multithreaded processing).
The benefits of Functional Programming
The example above is well representative of the benefits of functional programming. We can summarise these as follows:
- Readability. By expressing our intentions at a higher level than in the procedural style, we can show our intent.
- Conciseness. By the same token, we can implement the same functionality in fewer lines of code. Fewer lines means fewer bugs (certainly fewer characters you can mistype), so provided we retain readability conciseness is a good thing.
- Intrinsic support for parallelism. Even if there’s no magic parallelism happening behind the scenes, functional programming is inherently well suited to multithreaded computing and carrying out different work strands in parallel. This is because its focus on pure functions means that we generally have immutable data structures – that is, data structures whose contents never change. As pure functions do not modify any state of the program, two functions never try to modify the same shared state concurrently, thus avoiding race conditions.
Data not changing doesn’t mean we can’t perform interesting computations. We can create new data structures which are
different from the original ones, but we avoid modifying
those that we have already created. In the examples above the input
and output
lists were never modified in either
case, but the procedural code had a list
variable that was modified, and the functional style eliminated this.
The limitations of Functional Programming
Clearly, there are some spheres in which Functional Programming fits naturally and makes obvious sense. For instance,
complex banking calculations will naturally fit the functional pattern because they are all about performing
calculations on data. Even less obvious programs generally have elements which can be expressed more clearly and
concisely in the functional style – a large proportion of for
and foreach
loops could be replaced with functional
alternatives, as in the example above.
However, strict adherence to Functional Programming principles leads to challenges that make it harder to write many programs. For instance, if side effects are outlawed how can we read user input or write files to disk? Truly functional languages have workarounds for this that “hide” the side effect behaviour in a way that allows you to treat absolutely everything as an apparently pure function call.
The great thing about languages like C# is that they provide support for functional concepts when useful, but still let
you use the procedural style when that makes more sense. Hence you can use Console.WriteLine
and accept that it’s not
very functional in style, but couple it with a neat series of chained function calls to calculate the value to write.
The golden rule is as always: look for the programming style that lets you write a given bit of code in the simplest and most expressive manner possible. That’s often, but certainly not always, a functional style.
Immutable data structures
We mentioned above the benefits of avoiding changes to data. The best way to ensure this guideline is followed is to
create immutable data structures – ones that cannot be changed. In C#
this is just a case of ensuring that all fields are set in the constructor, and cannot be modified subsequently (so
no set
on properties, no public fields, and the public methods on the class should never modify its private
variables).
It is worth noting that the vast majority of common classes in the .NET libraries are not immutable. For
example List<T>
, Dictionary<TKey,TValue>
, etc. This is largely because .NET is not always well optimised for
immutability (creating new objects is relatively expensive), and so using mutable data structures will achieve better
performance if you’re doing a lot of operations.
However, where you are not doing anything performance critical and want immutability, you are not stuck with creating
your
own classes. The secret is to use an appropriate interface that shows your intent to be immutable and prevents code
from changing data when you don’t want it to. So for instance you may well create a List<int>
, but if you don’t need
to subsequently change it you should use IEnumerable<int>
in your code – an interface which supports iterating over
the members of the list, but not making changes to it. Thus you get the benefit of using immutable data structures in
most of your code, without losing the flexibility to, for instance, take advantage of mutation when initially creating the
list.
Iterator blocks
Iterator blocks are a powerful way to create immutable lists of data dynamically, on demand. This can be a useful technique to bridge the procedural and functional styles.
Here’s an iterator block that generates an infinite stream of even numbers:
public IEnumerable<int> Evens
{
get
{
int next = 0;
while (true)
{
yield return next;
next += 2;
}
}
}
The magic is in the yield return
. Every time someone tries to examine the next integer in the list, this method will
be executed until it hits a yield return
, at which point it will provide that value to the caller and pause until more
of the list is read.
You can use this in any code that takes a list. For example to add up the first 10 even numbers:
var sum = Evens.Take(10).Sum();
(The functional style again – we could have implemented the same thing more verbosely with a for
or foreach
loop)
Needless to say you should never run this:
var infiniteSum = Evens.Sum();
Since Evens
represents an infinitely long list of numbers, this code will never complete. (Or more likely will hit
an overflow exception when the sum becomes too large for an int
.)
Iterator blocks are not just for infinite lists. Here’s one that reads a file line by line, naturally stopping when the file has been entirely read:
private IEnumerable<string> ReadLines(string filename)
{
using (var reader = new StreamReader(filename))
{
while (!reader.EndOfStream)
{
yield return reader.ReadLine();
}
}
}
It’s worth understanding what’s going on under the cover, so you can correctly determine how a program will execute. Consider the following:
var lines = ReadLines(filename);
Console.WriteLine(lines.Take(1));
Console.WriteLine(lines.Take(1));
What does this do? The answer is that it prints the first line of the file, twice. It does not print the first two
lines. It also carries out the processing involved in reading the file twice, so there are, in total, two
calls to new StreamReader
, and so on.
Why does this happen? When you define an iterator like this, behind the scenes the compiler creates a class which
implements IEnumerable<string>
. The lines
variable in the example is an instance of this class. When you try to
iterate over this enumerable, for example via a foreach
or one of the LINQ methods (which themselves use foreach
behind the scenes), .NET invokes this class’s GetEnumerator
method. This returns a new instance
of IEnumerator<string>
, which again has been auto-generated by the .NET compiler and implements the body of
the ReadLines
method. Thus the two separate calls to Take
above both create an instance of the enumerator, and it’s
that instance that has the bulk of the file-reading behaviour in it – hence this code gets executed twice.
It is relatively rare that this is the behaviour you actually want.
-
If this is the output you want, you probably don’t want to actually read the file twice. The standard pattern to prevent this is to add a
.ToList()
to the line that createslines
. That way you fully evaluate the iteration once, and use the regular list of results at the end. -
If you actually want to return the first line, and then the second line, you’ll need to implement this explicitly. For example:
var firstTwoLines = ReadLines(filename).Take(2).ToList(); Console.WriteLine(firstTwoLines[0]); Console.WriteLine(firstTwoLines[1]);
Lazy evaluation
The iterator block examples used above are an example of lazy evaluation. This is a useful concept in functional programming and allows you to, for example, implement infinite lists, or just avoid executing expensive code until it’s strictly necessary.
Lazy evaluation can help reducing coupling in your code. Coupling is where one module depends on another module’s code. Consider the infinite list of even numbers; without using lazy evaluation, you would need to pass the number of even numbers required into the method as a parameter. You have to complicate your even number code (albeit only slightly), to take into account the requirements of the caller. Consider what happens if, rather than taking the first 10 even numbers, you want to ask for “all the even numbers less than 100”? That requires different code in your method – the change to how you use the list of numbers has caused a change to the code that creates the list. Lazy evaluation via the iterator block avoids this – you just implement an infinite list, and let the calling code deal with the consequences.
There are two risks to bear in mind. Firstly, you might accidentally try to consume an infinite list. While in many
cases your list won’t be infinite, so this concern won’t apply, you might still run into the “multiple evaluation”
problem (the example above where we accidentally create the StreamReader
twice).
Secondly, you lose control over precisely when your code gets executed. When it’s just generating a list of numbers
that’s fine. But suppose you had some other code that created the file whose lines are read by ReadLines
. The
behaviour of the ReadLines
iterator will vary depending on whether you create the file before, during or after you do
a foreach
over the output from ReadLines
– and this will happen at some arbitrary time, outside of the control of
the code in the ReadLines
method. There are new and unusual failure cases to consider, such as what happens if the
file gets deleted half way through the operation. If you’re building some code that uses lazy evaluation, make sure you
think about what will happen when the code actually executes, not just when you call into the iterator block in the
first place.
LINQ
We have used several examples of LINQ methods in the examples above, and you have probably used it in your earlier programming as well. Let’s briefly review what LINQ actually is.
LINQ stands for Language INtegrated Query. It was originally used mostly for providing a more fluent language for reading from databases, akin to the Structured Query Language used by database engines such as Microsoft SQL Server. However, it rapidly evolved into a more general functional approach to dealing with lists of data in C#.
LINQ consists primarily of a library of methods on IEnumerable<T>
. These are all functional in nature (they don’t
allow side-effects). The most common and most useful are:
Where
– filter a listTake
andSkip
– use or discard the first n elements in the listFirst
andLast
– pick the first or last element in the listCount
– count how many elements there are in the list (optionally only those matching some condition)Contains
– test whether a list contains a particular valueSelect
– create a new list by applying some operation to each element in the original list, in turnAggregate
– iterate over the list maintaining a “running total” type resultGroupBy
– group the elements in a list into sub-groups based on some criterionOrderBy
– create a new list containing the elements of this list sorted into some orderToList
,ToArray
andToDictionary
– convert this list into aList<T>
, array, orDictionary<TKey,TValue>
There are many more. Most of these are fairly straightforward and you can see how they work just by playing about. We’ll
consider Aggregate
and GroupBy
in a bit more detail below since they’re harder to get to grips with.
Those familiar with similar concepts in other programming languages, or mathematics, may recognise Where
as
filter
, Select
as map
and Aggregate
as fold
.
Note that in the descriptions above, we have referred to “lists”. In fact, there is nothing special about a “list” in
this context – we mean anything that implements IEnumerable<T>
. That might be a List<T>
, or it might be some
special
object that represents a database access layer. It might even be an array, since the compiler recognises arrays as
implementations of IEnumerable<T>
.
Aggregate
The Aggregate
operation (also known as fold
, or fold left
) is used to apply some operation to each element in
turn, maintaining a “running total” as you go. The final result is the value of that running total at the end of the
list.
Here’s an example of how to add up all the integers in a list (although note that in practice you would use the
equivalent LINQ method Sum
!):
var sum = list.Aggregate(0, (acc, v) => acc + v);
To break this down:
- The
0
is the “seed” – the starting value of our running total. If there are no elements in the list, this is the value that gets returned. - The
(acc, v) => acc + v
is our aggregation function. Here it’s a lambda expression, but it could be any method reference. This gets executed once for each item in the list in turn; for the first item we pass in0
foracc
and the first item in the list asv
; then we pass in the result of that call asacc
for the next item in the list, and so on.
This can be a useful and concise way of expressing certain operations on lists. It’s a way of squishing all the values in the list down into a single result, taking just one element at a time.
GroupBy
GroupBy
allows you to create subsets of a list based on some criteria. For example, consider the following list:
var apples = new List<Apple>
{
new Apple { Colour = "Red", Poisoned = true},
new Apple { Colour = "Red", Poisoned = false},
new Apple { Colour = "Green", Poisoned = false}
};
Suppose you wanted to group the apples together by colour. You would achieve this as follows:
var groups = apples.GroupBy(apple => apple.Colour);
Console.WriteLine(groups.Count());
var redApples = groups.Single(grouping => grouping.Key == "Red");
Console.WriteLine(redApples.Count());
The first WriteLine
will print “2” – there are two groups, one of red apples and one of green. The second WriteLine
will print “2” as well – there are two apples in the red group.
The return value fromGroupBy
can be a little confusing. It’s anIEnumerable<IGrouping<TKey,TSource>>
. In other words,
a “list of groupings”. Each “grouping” has aKey
property which returns the key value that caused all the items in that
grouping to be grouped together. Confusingly there’s noValues
property or equivalent – theIGrouping
itself
isIEnumerable
and hence you just iterate over the grouping itself to see its contents:
Console.WriteLine(redApples.Key);
foreach (var apple in redApples)
{
Console.WriteLine(apple.Poisoned);
}
GroupBy
is often most useful in conjunction with ToDictionary
, since you can use it to create a lookup table:
var applesLookup = apples
.GroupBy(apple => apple.Colour)
.ToDictionary(grouping => grouping.Key, grouping => grouping);
Console.WriteLine(applesLookup["Red"].Count());
Note that the ToDictionary
call needs two things, a way to work out the dictionary key (the same as the grouping key
in this case), and then a way to work out the dictionary values (here, the grouping itself, i.e. the list of apples of
this colour). The grouping => grouping
can in fact be omitted in this case – using the contents of the input list as
the values in the dictionary is the default behaviour.
Query comprehension syntax
The LINQ examples we have used here are written in standard C# syntax. There is an alternative “query comprehension syntax” which endeavours to look rather more like the SQL typically used to query databases. So you could replace this:
var poisonedRedApples = apples.Where(apple => apple.Colour == "Red").Select(apple => apple.Poisoned);
with this:
var poisonedRedApples =
from apple in apples
where apple.Colour == "Red"
select apple.Poisoned;
This rarely adds significantly to readability, but might be recommended by some coding standards. You should at least be aware of the existence of this syntax, so you can understand it if you see it.
Other things you can use LINQ on
As mentioned earlier, LINQ is not restricted to use on simple C# data structures such as lists. One classic use case is
in talking to SQL databases, via “LINQ to SQL”. Given some suitable db
object representing your database, and assuming
you have a Users
table, you could write this:
db.Users.Where(user => user.Name == "John")
.Select(user => user.LastLogin);
For all intents and purposes, this works in exactly the same way as the same LINQ operators on a list.
However, behind the scenes something more subtle is going on. Using list operations would require fetching the
entire Users
table from the database into memory, which is potentially a very expensive operation. SQL databases allow
much more efficient operations, such as SELECT LastLogin FROM Users WHERE Name = 'John'
. The above C# will actually be
translated by LINQ to SQL into this efficient SQL query, rather than being executed as a list operation.
How does this work? In short, because the Where
method in this case (which is actually on IQueryable<T>
,
not IEnumerable<T>
) doesn’t take a Func<User,bool>
as you might expect, but an Expression<Func<User,bool>>
.
The Expression
type tells the compiler to pass the C# code expression user.Name == "John"
into the method, instead of
evaluating that expression and creating a function as instructed by the lambda expression. The Where
method can then
pick apart this C# code and convert it into the equivalent database command.
Inevitably this limits the expressions you’re allowed to pass in (you must use a simple lambda expression such as the example, not a more complex method call). But it’s an impressively powerful feature.
Further reading
Microsoft Virtual Academy’s A Guide to Object-Oriented Practices course has a section on Functional Concepts which covers some of this material.
Learning C# 3.0 has some slightly dated material on LINQ in Chapter 21.
Head First C# provides a more up-to-date examination of LINQ in Chapter 14, although focussing primarily on the “query comprehension syntax”.
Other programming paradigms
A programming paradigm is a way of structuring and expressing the code that implements a software development solution. This module has been focused on the functional programming paradigm, and it should be clear how that differs from other ways of programming that you’re familiar with. There are many other programming paradigms, but the following are the most important at this stage of your learning.
- Procedural
- Code is structured as a set of procedures
- This is a form of imperative programming, which means that the steps that the computer needs to follow are expressed in order
- In procedural programming, you approach a problem by deciding how the necessary logical steps should be divided up
- This used to be the dominant programming paradigm, before the widespread adoption of object-oriented programming
- Object-oriented
- Code is structured using classes and objects
- As explored in the Object-Oriented Programming module, some of the strengths of this approach are:
- Encapsulation of an object’s properties (data) and behaviour (methods) into a single entity
- Abstraction, which means hiding details of an object’s implementation behind its interface
- Inheritance of properties and methods from an ancestor class
- Polymorphism, through which an object can be interacted with as if it were an instance of an ancestor class, while it behaves according to its actual class
- In object-oriented programming, you approach a problem by dividing it up into entities (objects) that have particular properties and behaviour
- This is a very popular programming paradigm for general-purpose software development, particularly as there are OO patterns for many common situations – e.g., MVC (Model-View-Controller) is an OO pattern for application development
- Functional
- As this module has explored, code is structured using functions
- As much as possible, functions are pure and do not have side effects (input/output are exceptions to this)
- Programs are made up of functions composed together so that the output of one is the input for another
- This can make programs more readable
- In functional programming, you approach a problem by breaking down the computations into stateless (pure) functions comprised of other functions
- Functional programming is especially suited to complex manipulation of large amounts of data
- As this module has explored, code is structured using functions
- Event driven
- Code is structured as units that are executed in response to particular events
- We will see some event driven programming in the Further JS: the DOM and Bundlers module, because the JavaScript code that runs in a web browser is executed in response to events (e.g., when a button is clicked or an API call responds)
- In event driven programming, you approach a problem by identifying the events and defining the response to each one separately; the important concept to keep in mind is that the “flow” of the program logic is controlled by external events
- This is particularly suited to game development and device drivers
It used to be the case that each programming language had a particular paradigm that needed to be followed. There are still some languages like that, but many modern languages support multiple paradigms – as this module and the Object-Oriented Programming module have shown.