Chapter 11. Practical Functional C#

I’m not just a pretty face.1 In addition to spending my days slogging at the virtual IT coalface each day, I’ve also been privileged enough to spend a lot of time over the years talking at various events on the subject of functional programming with C#. While at these talks, a few questions come up on a fairly regular basis.

The most common is “Why don’t we just use F#?” See all the way back in “What About F#? Should I Be Learning F#?” for my answer to that particular question. It comes up at just about every event I’ve ever spoken at, which is one of the reasons I gave such a detailed answer.

Oddly, the second most common question is to explain monads (which I did in Chapter 7). Hopefully, after getting to this point, you’re something of an expert on that yourself now.

After these, the next most common question is about performance. There’s a widespread belief that FP in C# is inefficient in production code compared with OOP. I’ll spend the first section of this chapter talking about performance and whether it’s an issue that you need to be concerned about before adopting functional C# in your everyday life—or, at least, your everyday life that involves .NET code. For me, there’s a rather large overlap between those two things.

Functional C# and Performance

Let’s continue now with a look at functional C# and performance. To do that, we’re going to need a bit of code to use as a test subject, so we can compare imperative code (i.e., the programming paradigm that OOP belongs to) to the various flavors of functional C#.

Now, I’m a big fan of the annual Advent of Code coding event,2 and the very first challenge ever published in its first event in 2015 is one I often link people to as a good example of how functional thinking can make a difference. Let’s walk through that challenge.

The input is a string consisting strictly of the characters ( and ). These represent the movements of an elevator. A ( represents a movement up a floor, and ) represents a movement down a floor. We start on the ground floor, represented not by the letter G for “ground,” but a 0, meaning we can work exclusively with an integer value to represent the current floor, with negative numbers representing floors beneath the ground. (Looking at some of the puzzle inputs, this particular building must have so many basement levels that it’s in danger of making magma from the Earth’s mantle a recurring problem. Never mind, though; it’s a bit of fun, not an architectural blueprint.)

The challenge has two parts, which will serve perfectly for performance tests. The first is to run a series of instructions and calculate the final floor. The second is to work out which character of the input string gets you to floor –1 (the basement).

Here’s an example for you. Given the input string ((()))))((((, we go up three floors (+3), then down five floors (–5), then finally up four floors (+4). The answer for this part the puzzle is the sum of all of these changes, so 3 – 5 + 4. The answer is 2, the floor the elevator finishes on after carrying out all the operations requested by the puzzle input.

To complete part 2, you need to determine which character of the input string is the one that first puts the elevator at floor –1. In Table 11-1, we can follow the sequence of instructions in turn.

Table 11-1. Instructions for the elevator and their resulting floors
CharacterInstructionFloorNote

0

(

1

1

(

2

2

(

3

3

)

2

4

)

1

5

)

0

6

)

–1

First reached floor –1 here!

7

)

–2

8

(

–1

Reached it again here, but only the first time counts

9

(

0

10

(

1

11

(

2

For part 2, the answer is 6: the seventh character is the first to put us on floor –1, but the array location is 6, because it’s a zero-based array.

These two puzzles are great examples of both definite and indefinite loops, and the input provided by the puzzle is large enough (over 7,000 characters) that it’ll cause the code to need to work for a bit—enough for me to gather some statistics.

If you care about spoilers, go ahead to the web page and solve the puzzle before continuing. Just know that if you use a functional approach, you can solve it in a single line!

Note

OK, here’s a spoiler warning now: after this section, I’m launching into a full-on spoiler-heavy set of solutions to this eight-year-old puzzle.

Baseline: An Imperative Solution

Before delving into performance in the various functional solutions I have prepared, let’s first explore how performance looks in an imperative solution. This is something of a scientific experiment, and to be a proper experiment, we need a control—a baseline to compare all the functional results to.

For performance measuring, I’m using Benchmark.NET with .NET 7. For those of you not familiar with this tool, it’s similar in some ways to unit testing, except it’ll compare the performance of several versions of the same code. It runs the same code many times, in order to get a mean of things like time taken to run and the amount of memory used.

The level of performance may vary among versions of .NET too if you try this yourself in something other than .NET 7, as performance improvements are added all the time by Microsoft.

The following are solutions to the two puzzles entirely using imperative-style coding:

// i.e., the definite loop
public int GetFinalFloorNumber(string input)
{
    var floor = 0;
    foreach(var i in input)
    {
        if(i == '(')
            floor++;
        else
            i--;
    }
    return floor;
}

// i.e., the indefinite loop
public int WhichCharacterEntersBasement(string input)
{
    var floor = 0;
    var charNo = 0;

    foreach(var i in input)
    {
        charNo++;
        if(i == '(')
            floor++;
        else
            floor--;

        if(floor == -1)
            return charNo;
    }
}

There might be better solutions, but this one will serve.

Performance Results

Now, these tests were performed for a 7,000-character input on my developer’s laptop, so the actual numbers you might see when trying to replicate this experiment are likely to vary. The main point of the next few sections is to compare the results from the same test setup.

Imperative baseline results

Table 11-2 shows my results for this imperative solution.

Table 11-2. Object-oriented performance results
Loop typeMean time takenTime taken standard deviationMemory allocated

Definite

10.59 μsa

0.108 μs

24 bytes

Indefinite

2.226 μs

0.0141 μs

24 bytes

a These are microseconds.

Despite the size of the task, the time taken is small indeed. Not too shabby. The indefinite loop is faster, but we’d expect that—it’s not having to loop through the entire input string.

In the next sections, we’re going to go through a few FP implementations of each loop type and see what difference it makes to performance.

Definite loop solutions

I did say that it was possible to solve this in a single line, didn’t I? It’s a fairly long line, but a single line nevertheless. Here’s my line:

public int GetFinalFloorNumber(string input) =>
    input.Sum(x => x switch
    {
        '(' => 1,
        _ => -1
    });

I’ve added some newline characters to make the code readable, but it’s still technically a single line!

This code is performing a Sum() aggregation method that adds either 1 or –1 at each iteration, based on the current character. Bear in mind that in C#, string is both a bit of text and an array, which is why we can apply LINQ operations to it like this. Table 11-3 shows the effect this has on performance.

Table 11-3. Sum() aggregation performance results
SolutionMean time takenTime taken standard deviationMemory allocated

Imperative baseline

10.59 μs

0.108 μs

24 bytes

Sum aggregation

60.75 μs

0.38 μs

56 bytes

There’s no avoiding that performance here is indeed worse. This is just out-of-the-box LINQ, so even using one of the Microsoft-provided tools, the code still doesn’t run as quickly as the imperative version.

What do you think, should we throw in the towel now? Not likely! I have a few more things for us to try. How about if we separate out the conversion of charint into two separate lines?

public int GetFinalFloorNumber(string input) =>
    input.Select(x => x == '(' ? 1 : -1).Sum();

Does this makes any difference? Let’s examine Table 11-4 to find out.

Table 11-4. Select() then Sum() aggregation performance results
SolutionMean time takenTime taken standard deviationMemory allocated

Imperative baseline

10.59 μs

0.108 μs

24 bytes

Select/sum aggregation

84.89 μs

0.38 μs

112 bytes

Well, that’s a little worse. OK, how about converting to another data structure, like a dictionary, which has a fantastic reading speed:

public int GetFinalFloorNumber(string input)
{
    var grouped = input.GroupBy(x => x).ToDictionary(x => x.Key, x => x.Count());
    var answer = grouped['('] - grouped[')'];
    return answer;
}

This time, we’re creating an IGrouping in which each possible char value is one Group within it. In our example, there will always be two Groups with a key of either ( or ). Once we have the groupings, we’re deducting the size of one group from the other to get the final floor (i.e., deducting the total number of moves down from the total number of moves up).

As we can see in Table 11-5, not only is that even worse, but the amount of allocated memory is horrible.

Table 11-5. Group then insert dictionary performance results
SolutionMean time takenTime taken standard deviationMemory allocated

Imperative baseline

10.59 μs

0.108 μs

24 bytes

Group/dictionary aggregation

93.86 μs

0.333 μs

33.18 kilobytes

I’d still like us to try an indefinite loop, and a few more things before I wrap up with my thoughts on what all this means. For now, I don’t think this is as bad as it looks. Keep reading to see what I mean.

Indefinite loop solutions

Now we’re going to try out a few solutions for the indefinite loop puzzle—i.e., which character of the input string is the first to bring us to floor –1. I can’t say where that’ll be, but we’ll need to loop until the condition is met.

First, I’ve always heard that recursion in C# was a bad idea if you value your stack. Let’s see just how bad things could be. This is a recursive solution to the problem:

public int WhichCharacterEntersBasement(string input)
{
    int GetBasementFloor(string s, int currentFloor = 0, int currentChar = 0) =>
        currentFloor == -1
            ? currentChar
            : GetBasementFloor(s[1..], s[0] ==
                '(' ? currentFloor + 1 : currentFloor - 1, currentChar + 1);

    return GetBasementFloor(input);

}

This code is nice, neat, and compact. Table 11-6 shows what the performance is like.

Table 11-6. Recursive loop performance results
SolutionMean time takenTime taken standard deviationMemory allocated

Imperative baseline

2.226 μs

0.0141 μs

24 bytes

Recursive loop

1,030 ms

4.4733 μs

20.7 megabytes

That’s honestly quite shocking. Note that those time results are in milliseconds, not microseconds. That’s nearly half a million times worse! The memory usage is also too large to be stored on a box of old floppy disks.

You now have the evidence before you, if it were needed, that recursion is a really bad idea in C#. Unless you’re very sure of what you’re doing, I’d tend to avoid it altogether.

What about nonrecursive functional solutions? Well, these are the ones that require a compromise of some sort. Let’s start with the use of our IterateUntil() function from “Trampolining”. How does that affect performance?

This is our code:

public int WhichCharacterEntersBasement(string input)
{
    var startingInput = new FloorState
    {
        InputString = input,
        CurrentChar = 0,
        CurrentFloor = 0
    };

    var returnValue = startingInput.IterateUntil(x =>
     x with
    {
        CurrentChar = x.CurrentChar + 1,
        CurrentFloor = x.InputString[x.CurrentChar] ==
         '(' ? x.CurrentFloor + 1 : x.CurrentFloor - 1
    }
    , x => x.CurrentFloor == -1);

    return returnValue.CurrentChar;

}

public record FloorState
{
    public string InputString { get; set; }
    public int CurrentFloor { get; set; }
    public int CurrentChar { get; set; }
}

This time, we need something to track state with. We’re trying a record type, since they’ve been provided to allow more functional code to be written. Table 11-7 shows the results.

Table 11-7. Trampolining performance results
SolutionMean time takenTime taken standard deviationMemory allocated

Imperative baseline

2.226 μs

0.0141 μs

24 bytes

Trampolining

24.050 μs

0.3215 μs

55.6 kilobytes

Not so shocking this time, but still worse than the imperative version. Quite a large amount of memory is still being stored during the operation too.

How about if we were to swap record out for the previous functional-style structure Microsoft provided: the tuple. Would that improve performance? Let’s take a look:

public int WhichCharacterEntersBasement(string input)
{
    var startingInput = (InputString: input, CurrentFloor: 0, CurrentChar: 0);

    var (_, _, currentChar) = startingInput.IterateUntil(x =>
    (
        x.InputString,
        x.InputString[x.CurrentChar] ==
         '(' ? x.CurrentFloor + 1 : x.CurrentFloor - 1,
        x.CurrentChar + 1
    ), x => x.CurrentFloor == -1);

    return currentChar;
}

That’s unfortunately not quite as friendly to look at—at least not if you’re mostly an OOP developer. I adore the lovely syntactic sugar that record brings to us, but if performance is our goal, readability and maintainability may have to be sacrifices on its altar.

If you look at Table 11-8, you’ll see the performance results.

Table 11-8. Trampolining with tuples performance results
SolutionMean time takenTime taken standard deviationMemory allocated

Imperative baseline

2.226 μs

0.0141 μs

24 bytes

Trampolining with tuples

17.132 μs

0.0584 μs

24 bytes

That’s substantially better. The time taken is still a little worse, but the amount of memory allocated is exactly the same!

The last test I want us to do is the custom IEnumerable option I demonstrated in “Custom Iterator”. Let’s see how that compares to trying a ton of terrific trampolining tuples:

public class LiftEnumerable : IEnumerable<int>
{
    private readonly string _input;

    public LiftEnumerable(string input)
    {
        this._input = input;
    }

    public IEnumerator<int> GetEnumerator() => new LifeEnumerator(this._input);

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class LifeEnumerator : IEnumerator<int>
{
    private int _currentFloorNumber = 0;
    private int _currentCharacter = -1;
    private readonly string input;


    public LifeEnumerator(string input)
    {
        this.input = input;
    }

    public bool MoveNext()
    {
        var startingFloorNumber = this._currentFloorNumber;

        this._currentCharacter++;
        this._currentFloorNumber = startingFloorNumber == -1
            ? -1
            : this.input[this._currentCharacter]

        return startingFloorNumber != -1;
    }

    public void Reset()
    {
        this._currentCharacter= -1;
        this._currentFloorNumber = 0;
    }

    public int Current => this._currentCharacter + 1;

    object IEnumerator.Current => Current;


    public void Dispose()
    {
    }
}


// The actual code call
public int WhichCharacterEntersBasement(string input)
{
    var result = new LiftEnumerable(input).Select(x => x);
    return result.Last();
}

That’s an awful lot of code, but is it any faster? See Table 11-9 to find out.

Table 11-9. Custom enumerable performance results
SolutionMean time takenTime taken standard deviationMemory allocated

Imperative baseline

2.226 μs

0.0141 μs

24 bytes

Custom Enumerable

24.033 μs

0.1072 μs

136 bytes

The time taken is pretty much identical to the trampolining example with a record, but more data is allocated in this version. The results are not too bad and still nowhere near the calamity that is the recursive version.

Another option is to try to reference an F# project from a C# project (known as an interop) to have guaranteed functional code when C# really won’t play along. Let’s have a look at that.

Interop with F# performance

It’s possible to write code in an F# project and reference it in C# as if it were just another .NET library. The process is fairly simple, and I’ll walk you through it.

Create a new project in your Visual Studio solution, but instead of a C# library, select F# from the drop-down options.

You can reference the F# project in your C# code. Note that the code you’ve written won’t be visible in C# unless you compile the F# project separately because F# uses a different compiler than that used by C#.

Since this is a C# book, I’m not going to go into how to write F#, but it’s interesting to see how the performance would compare if we absolutely had to have a piece of entirely functional code without having to compromise with the limitations of functional C#.

In case you’re curious, here is an F# solution to the problem—don’t worry if you don’t understand exactly how it works. I’m presenting it here as a curio rather than something you need to learn:3

module Advent =
    let calculateFinalFloor start input =
     input
        > Seq.fold (fun acc c -> match c
            with | '(' -> acc+1 | ')' -> acc-1 | _ -> acc) start


    let whichStepIsTheBasement start input =
     input
        |> Seq.scan (fun acc c -> match c with
            | '(' -> acc+1 | ')' -> acc-1 | _ -> acc) start
        |> Seq.findIndex(fun i -> i = -1)

This code is purely functional. Seq is the F# equivalent of IEnumerable, so some neat efficiency-saving features are at work here because of the lazy-loading nature of both of those types.

Given this is a C# book, I’m interested only in the performance effects on a C# project. F# handles these scenarios very efficiently in an F# project, but that’s beyond the scope of this book. Let’s look at the option available to interop F# code into C#.

What do I mean by this? Well, F# and C# both compile into the same .NET IL, meaning that once compiled, they not only look the same but can also reference each other. From a C# perspective, the F# code can appear available in C# code as a static function.

But what about the performance? Will the efficient nature of F# be countered somehow by being referenced over a C# channel? Let’s have a look at Table 11-10 to see the results.

Table 11-10. F# interop performance results
Loop typeSolutionMean time takenTime taken standard deviationMemory allocated

Definite

Imperative baseline

10.59 μs

0.108 μs

24 bytes

Definite

F# interop

63.63 μs

0.551 μs

326 bytes

Indefinite

Imperative baseline

2.226 μs

0.0141 μs

24 bytes

Indefinite

F# interop

32.873 μs

0.1002 μs

216 bytes

That’s still worse, it turns out. It’s not the worst of the options we’ve tried, but it’s still not great. The indefinite loop example is 15 times worse than the original (control) imperative version. Even if you’re happy with the performance hit going down this route will require learning F# first, and that’s even further outside the scope of this book. At least you know F# is there in the future if you enjoy functional-style C# enough that you want to take it further still. You might need to make it an F#-only project if you want to enjoy the full benefits, however. As you can see here, performance issues arise when interoping with C#.

External factors and performance

Believe it or not, everything we’ve looked at so far is incidental in comparison to making any sort of interaction with the world outside your C# code. Let me show you what I mean.

In this next experiment, we’ll modify both the original OOP baseline functions and the most efficient version of the FP solution, the one that used tuples. We’ll set them not to accept any input strings, but instead to load the same data from a file stored in the local Windows filesystem. Functionally, we have the same input and same result, but a file operation is involved now.

What sort of difference could that make? Well, Table 11-11 just so happens to contain the answer.

Table 11-11. File process performance results
Loop typeSolutionMean time takenTime taken standard deviationMemory allocated

Definite

Imperative baseline

10.59 μs

0.108 μs

24 bytes

Definite

Imperative with file

380.21 μs

15.187 μs

37.8 kilobytes

Definite

FP with file

450.28 μs

9.169 μs

37.9 kilobytes

Indefinite

Imperative baseline

2.226 μs

0.0141 μs

24 bytes

Indefinite

Imperative with file

326.006 μs

1.7735 μs

37.8 kilobytes

Indefinite

FP with file

366.010 μs

2.2281 μs

93.22 kilobytes

How about that? The functional solutions still take a little longer, but the proportion of difference is far less. When we compare the in-memory version of the FP solution with a definite loop using tuples to the imperative equivalent, the FP version takes about 8.5 times longer to complete. When we compare the two versions that include a file load, the proportional difference is only about 1.2 times slower—hardly that much more.

Imagine doing this if there were HTTP calls to web APIs or database connections established over a network? I can guarantee you that the time taken would be far worse than what we see here.

Join me, if you would, in the next section, where I’ll put together my final thoughts on the conclusions we can draw from these experiments.

What Does All of This Mean?

It’s an inescapable fact that functional C# is less efficient than well-written object-oriented code. That’s just the way it is. Of course, poorly written suboptimal code in any language or paradigm will always find ways to be horribly inefficient.

We can also show that pure LINQ operations, kept as compact as possible, are the most effective of the functional features. If a state object of some kind is required, a tuple is currently the best choice. This assumes that performance is the most important goal of our code. Is it always? It depends entirely on what you’re trying to achieve and who your customer is.

If you’re planning to make an elaborate virtual reality application with high-definition 3D graphics, then honestly, you want to steer well away from functional solutions altogether. For projects like that, you’ll need to squeeze every last bit of performance from your code that you possibly can and then some.

What about the rest of us? I’ve worked in a lot of companies and I would say that in nearly all of them, performance wasn’t necessarily the most critical driver behind my development work. Mostly what I’ve worked on throughout my career is bespoke web applications used by internal employees carrying out some sort of business process as part of their daily workload. Whenever a requirement has been handed to me, getting it completed and released to production quickly is usually more important than spending time worrying about performance.

I’ve argued previously in this book that FP is easier to learn than OOP. Now, whether you agree with that or not, I hope by this point that I’ve at least convinced you that once you’ve taken the time to learn FP, putting an application together this way is faster than writing purely OOP-style code.

Even if there were issues with the app slowing down because of coding style, these days it’s easy enough to pop onto Azure or AWS and click a button to add an extra virtual RAM chip to the virtual server, and the problem largely goes away. But what about the cost to the business for that nasty old RAM chip?

Well, what about it? Let me put it to you this way. If I’m right, and FP-style code really is more concise, more robust, and quicker to write than OOP-style code, that would mean we can get changes made and shipped to production quicker, and with a greater chance that everything will work the first time without errors. Given that fact, what would cost the company more? One extra virtual RAM chip, or the amount per hour that your time costs as you do all the extra development work and resolve the unnecessary bugs that would otherwise end up in production?

In any case, even though I’ve talked about the potential need for more RAM, would we necessarily even need it? In the experiments we’ve done in the previous sections of this chapter, we can see that although the functional solutions need more time than the object-oriented equivalent, the proportional difference once we invoke a file operation isn’t really all that much. Hardly enough to start getting worried about.

Figure 11-1 shows the performance results of the two solutions that used files as input. The two sections of each pie chart represent the time required to load the file and the time required to process the data that was loaded.

images/fpcs_1101.png
Figure 11-1. Comparing performance of tests with file operations

The difference doesn’t look quite as bad when you see it in perspective, does it? Unless, that is, you really are working on a project that has financial consequences to every tiny loss in performance. This said, the stateless, side-effect-free nature of functional-style code strongly supports parallel processing. It might be easier to speed things up by standing up more instances of your application to get everything done quicker in a way that OOP tends not to support as well.

If you’re less utterly, singularly focused on performance, and you’d be happier with nicer code that’s easy to maintain, I’d suggest that any marginal loss in performance is a price worth paying to be able to write code in this style. Code spends whole orders of magnitude more time being read and maintained than it does being written in the first place. Having clearer, less error-prone code that you can end up with working within the functional paradigm is a very real benefit you can make use of.

As is always the case, it’s up to you: Your personal coding preferences. The constraints of your working environment and the team you’re part of. And, of course, whatever it is you’re trying to develop. You’re a grown-up,4 and I can’t possibly know the specific business drivers of your particular organization, so I’ll leave you to decide for yourself.

For the second part of this chapter, I’m going to consider a few practical concerns you might have regarding functional C# in a production environment, and what my opinion is on each.

Functional C# Concerns and Questions

Right, no time to waste. Let’s get cracking. First question, please!

How Functional Should I Make My Codebase?

One of the beauties of functional C# is that the functional content isn’t part of a framework. You can choose how much you want to do functionally or not, and that sliding scale can move just about as far to either side as you want it to.

You can decide to make your entire codebase functional-style. Or restrict it to a single project. A single class. A single function. Or, if necessary, a single line. Mix functional and nonfunctional in the same function. It can be done. As an entirely personal preference, I probably wouldn’t. But the point is you can, and there’s no reason why you shouldn’t.

I’m not a purist. I understand that sometimes in production all sorts of other concerns need to be considered beyond making the code exactly the way you want it.

You need to consider not just where you should be functional, but to what extent. Do you want to go all-out? Use partial application, monads, DUs, and the rest of it. Or you might feel like just using an awful lot of LINQ to replace while loops. That’s fine too—whatever you feel comfortable with. You don’t need to feel like a traitor to the cause.

Another consideration is the level of FP your colleagues are comfortable with. Do consider that however beautiful your functional code may be, it still has to be maintained by the team. I’d argue that monads aren’t really all that hard to understand, but you still need to convince everyone to learn how to use them before they’ll be ready to support code containing them—even if it might be in their best interests. You can lead a horse to water, and all that.

As I’m always saying, at the end of the day, the question you have to ask yourself is “What am I trying to achieve?” Once you’ve answered that truthfully, your decision-making process should be relatively easy.

You could always leave a few copies of this book lying around the office. See what happens. I don’t know how long after the publication date you’re reading this, but there’s even a chance that I may still be around. Feel free to get folks to reach out to me with questions.

How Should I Structure a Functional C# Solution?

You should structure a functional C# solution in classes and folders, largely the same as an existing OOP C# project. Strictly speaking, classes are not a functional concept. If you look at F# projects, they don’t necessarily have classes at all.

You can contain all your code in modules, but they aren’t really the same thing. They’re convenient ways for F# developers to group code. Modules are more like a C# namespace. There’s no functional meaning to a module.

In C#, on the other hand, there have to be classes. There’s no way around it, so that will still have to be a thing you do.

In pure functional-style C#, you could make every class and function static. If you did that, I feel sure you’re going to run into practical problems somewhere, especially when you have to try to interact with a nonfunctional class from NuGet or one of the built-in Microsoft classes.

Lack of dependency injection would also make unit testing far harder. Languages like Haskell get around this by using monads of various kinds. In C#, I’d say make your life easy and just use standard classes and an inversion-of-control (IoC) container, as hopefully you already have been.

A happy medium you could try is the Functional Core, Imperative Shell architectural model. Have all dependency injection carried out in the imperative outer layers; then the functional inner layers can be developed to have all dependencies passed in as parameters.

How Do I Share My Functional Methods Among Applications?

I maintain a set of functional libraries that are full of classes and extension methods, and these provide all my implementations of the monads, DUs, and other useful extension methods I want to use in my code. I usually have a separate project in the solution called Common, where I place anything generic like that. That way, the project can all sit somewhere in the codebase where I can use it, but I don’t necessarily have to look at it again while I’m getting on with my work.

The contents of my Common project are copied from solution to solution for the time being. At some point at work, we’re planning to set up our own local instance of NuGet, and when we do so, we’ll probably have the Common project set up as a consumable NuGet package. That’ll make it easy to distribute bug fixes, improvements, and new features. For now, copying over every time mostly works.

Did You Order This Pizza?

Erm, I’m not sure. That looks like it has got anchovies, so it’s probably not for me. I asked for a meat feast. Let me just check with Liam; I think it might be his. Give me five seconds, I’ll be right back.

How Do I Convince My Teammates to Do This Too?

Good question. When you work out how to do this consistently, let me know how you managed it.

I’ve been giving talks on this subject for a long time now, and reactions to FP seem to fall into one of a few categories:

  • OMG! This is the holy grail of development! I want moooooore!

  • Great ideas. I don’t want to commit to the whole functional paradigm, but I’m going to take a few bits that are especially cool away with me.

  • This is vaguely interesting, but I think I’ll carry on working the way I always have, thanks.

  • This is the worst. Boo!

I couldn’t give you any real statistics on how many people fall into each camp, but my feeling is it’s roughly a small number in the first and last group, and the vast majority in the other two in the middle.

That being the case, it’s our job as FP advocates to try to convince others of the benefit to changing their ways. Bear in mind, most human beings are creatures of habit who don’t really want to make huge changes to their daily lives like that, unless there’s a good reason.

The way to convince others also depends on where you work and what your project constraints are. If you’re creating mobile applications where every bit of memory is crucial, or a 3D graphics system for a virtual reality device and can’t afford even the tiniest bit of inefficiency in your code, then you probably will find FP quite a hard sell.

If, however, you don’t do those things, then there may well be a possibility you can convince everyone by talking about the benefits. Just don’t become an FP bore. At some point, it may become obvious there’s no traction with everyone else. Then it’s best to hold off and hope there’s a chance you can win the long game by attrition, rather than via a big, bold frontal attack.

The benefits I’d focus on, in roughly descending order, are as follows:

Reliability

Because functional-style applications are developed around the idea of consistent, predictable results with no side effects, they tend to be more robust and to consequently fail less. FP also massively enables unit testing. So by following this paradigm, you’ll end up with a higher-quality product that fails less. This will save the company a whole load of money in time that would otherwise be required to resolve bugs in production.

Development speed

It’s usually quicker and easier to write code using the functional paradigm (once you’re familiar with it, that is!), and enhancements tend to be especially easy because of the way that functional code is structured and tends to have fewer bugs. I’d talk about the amount of time and money that will be saved in the initial development effort, and in time no longer required following release to squash as many bugs as you might otherwise have to with OOP.

Domain-driven design

Although it’s not a stated goal of the paradigm, functional-style code tends to align well with DDD-style architecture, at least in comparison with OOP. If your team is interested in DDD, it’s perhaps worth mentioning that FP is a good fit in that case.

Microsoft support

It’s a stated goal of the .NET team to support the functional paradigm. Talk about how this style of programming isn’t abusing .NET; it’s actually using it the way it was intended (mostly).

Easy to learn

I hope you feel that way now, after getting to this point in the book. I don’t believe that FP is all that hard to learn, at least not once you dispense with all the formal definitions and scary-sounding terminology. In fact, adopting FP requires less to learn than a new developer faces in learning the object-oriented paradigm. Far more theory needs to be learned to fully master the OOP paradigm than FP. If you find OOP easier to grasp, you may be an experienced developer, well grounded in OOP after many years of experience. I don’t believe it takes as long to become proficient to the same extent with FP as it does with OOP. I’d reassure everyone that the learning overhead isn’t all that big.

Pick and choose

Mention also that you can adopt as much or as little of the paradigm as you want. You don’t need to throw away your old codebase and move to an entirely different one. You can start small by refactoring a single function or even just a single piece of a larger function, and you don’t even have to use monads (although I think you might get great results if you do!).

It’s not new!

It might finally be worth talking about how old the functional paradigm is. Many companies may be reluctant to adopt a new, trendy technology that hasn’t proven it’ll stay around long enough to be worth the investment. That’s a reasonable concern, but FP was first used as a software development concept in the 1960s, and its roots go back to the late 1800s. It’s already been proven a million times over in production environments.

I really wouldn’t necessarily start talking right away about monads, DUs, currying, or anything like that. Keep your points of discussion as familiar as you can.

Aside from talking about it yourself, there’s always this book and many more other good books on the subject. There’s a section coming up shortly in which I’ll recommend the ones I like the most.

Is It Worth Including F# Projects in My Solution?

Whether you include F# projects is up to you. Performance-wise, there’s no real issue with it I’m aware of.

The best usage might be to consider using F# for the deeper, rules-based parts of your codebase—for example, the functions that convert data from one form to another based on a set of business requirements. F# is likely to make those parts neater, more robust, and more efficient. Your code will also be a whole ton more functional than anything C# can do, if that’s what you’re after.

The only thing I’d consider is whether your team is willing to support F#. If so, great. Go for it. If not, get together with the team and have a discussion. That sort of decision needs to be made by everyone.

If your team isn’t comfortable with F#, you can at least take some comfort in the fact that most of the functional paradigm is still here in C#.

Will Functional Coding Solve All My Problems?

FP is unlikely to improve your poker game, read you a bedtime story, or make you cups of coffee when you’re in need. It certainly will provide you with a higher-quality codebase that’s easier to live with once it has been released to your production environment, will give you fewer live issues, and will be easier for new members of the team to learn when they join.

It’s still possible for someone who’s determined to, to write bad code, or to be lazy one day and make a mistake. Nothing on this planet can stop that from happening.5 Well, besides the usual methods of enforcing automated testing, code reviews, and manual quality checks—the sorts of things that have mostly been around since the beginning of development as an industry.

Functional-style coding will facilitate spotting problems, however. Its concise style makes it easy to tell at a glance what a function does and whether it’s doing what its name suggests.

Functional also—as stated earlier in this chapter—won’t be the most efficient solution to your coding requirement, but it’s near enough that unless absolute peak performance matters to you, it’s likely to be just fine.

None of this will help with any of your usual project management issues either. Issues with unclear requirements are between you and whichever business analyst you believe in.

FP will make you cool, though. Kids might even give you the thumbs-up in the street as you pass. Such is the street cred of FP. True fact.

Connery, Moore, or Craig?

None of the above, I’m a big fan of Timothy Dalton. He deserved more attention.

How Do I Think Through a Problem Functionally?

There isn’t one way think through a problem functionally any more than there’s one way to develop a piece of software. If it helps, though, I’ll briefly describe my process.

I would start by thinking through the logical steps of the code you’re trying to write. Try splitting it into the steps you’d describe if you were talking through your work with someone else: “First I’d do X, then I’d do Y, then Z.” This isn’t a way you can work especially well with object-oriented-style code, but it’s the best way to split up functional code.

I’d then write each piece of functional code based on those steps.

Wherever possible as well, I’d consider making whatever you’re working on an IEnumerable of some kind, whether that be of primitives, complex objects, or Func delegates. FP is often at its most potent when you’re running list-based operations, as in T-SQL.

I would advise against making chains of functions too long. You want opportunities when you’re developing to be able to examine the previous steps of a complex calculation, to make sure everything is working the way you expect. Splitting up long chains of functions also gives you chances to apply meaningful names to what you store at each stage of the process.

FP supports unit testing very well, so I’d also suggest breaking the entire process into as many smaller functions as you can, and then make sure you’ve tested each one as thoroughly as possible. If you have the advantage of being able to break the steps up logically, use it to the best effect you can.

What If There’s No Way to Make a Bit of Code as High-Performant as I’d Like with Functional-Style Code?

Dude, there are no rules. Make that bit imperative. Have a look at articles like those written by Steve Gordon on how to write high-performance C#. At the end of the day, you have to be pragmatic. I love functional-style code. It has an awful lot of benefit to bring to a project, but if there’s one business-critical function somewhere that is run thousands of times a second and needs every last drop of performance that can be had—do it in the way that makes the best sense. I won’t tell on you.

Summary

This chapter was a game of two halves.

In the first, we considered the myth of poorly performing functional code, and hopefully busted it to some extent. We saw that it’s a real phenomenon, but the difference is insignificant compared to any amount of I/O in your code.

In the second half, we considered some of the more esoteric, philosophical issues of FP in the industrial setting.

FP will return in You Only Live Twice Chapter 12, in which I’ll present options you have for doing FP by using third-party packages from NuGet.

1 OK, I’m not even that, but leave me my illusions, won’t you!

2 Two coding challenges per day for 24 days leading up to Christmas. I’ve never managed to get further than about day 14 in real time, but they’re fantastic puzzles, which I still continue to work on throughout the rest of the year.

3 Thanks once again to F# guru Ian Russell for this code.

4 Probably. Fair play to you if you aren’t and you’ve read this far. You’ll do well in life!

5 Probably nothing off it either, but who knows.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset