For me, 2019 was a year of learning as much as I could about software quality – to the extent that I launched a blog to share many of my findings. As a software engineering manager responsible for .NET development (among other things) on legacy applications, I suppose it was only natural that I would investigate F#.
Well, I did and it changed how I think about programming and software quality.
In this entry in the 2019 community F# Advent series I’m not going to give you a deep dive into one of the many awesome features of F#.
Instead, I’m going to tell you why you should take a good long look at F# and how it could fit into your team, the types of benefits it offers, and how it can change how you think about .NET development in general.
I got it Wrong
A year ago, one of my team members suggested we consider using F# in our code. I hadn’t seriously considered the language before, though I was aware of its existence.
Her argument was is a common one for F#: since much of what we work on is math related and F# has a reputation for being very good for mathematical operations as a functional language, we should consider F#.
In retrospect, I don’t think her argument was actually the right one to make. It’s wrong to characterize F# as only a math-oriented language or mathematical operations as the primary appeal of the language.
I decided that F# was wrong to adopt because its learning curve was too steep and its benefits were not significant enough for our projects.
I was wrong.
The True Appeal of F#
Now that I’m slightly older and hopefully wiser, let’s take a look at what I would have said to try to convince past Matt to investigate F#.
After that, we’ll look at some potential objections to F# and finish up with a discussion of how to integrate F# into an existing codebase and team.
If I could go back in time and talk to past Matt about F#, I wouldn’t highlight the math-oriented aspects of the language.
Instead, I’d start by highlighting some of the ways F# helps you ensure software quality at the language level.
Note: Code examples below from my article series on building a squirrel-based genetic algorithm in F#.
I’d start by talking about F#’s strong preference towards pure functions and immutability.
By making variables immutable by default, F# has removed entire classes of defects. It’s no longer difficult to tell what is modifying properties on an object, because objects are effectively mutated through reducer functions in simple easy to debug functions.
In the above example, the
decreaseTimer function creates a new version of the
state parameter with modified values. The original parameter’s values do not change.
Another benefit from immutability is that it removes the possibility of a property changing from being null to non-null or vice versa during an application’s lifetime.
Nulls and Options
Since nulls are a common error, I’d bring up F#’s handling of null values via Options (either some value or none). Options force you at a compiler level to account for null values. This effectively eliminates null reference exceptions.
In the above example, you can’t directly get
obstacle.Value because the compiler recognizes that it may be of
None type, forcing you explicitly to check for null in places where it is potentially possible.
Discriminated Unions and Matching
If I really wanted to sell the point, I’d talk about F#’s use of discriminated unions and how they can be used to represent a collection of possible types and states. The usage of discriminated unions is famously described as making bad states impossible to represent at the compiler level.
The above code represents a series of different types of actors, some of which can have additional state associated with them. This then lets us match in F# on types like so:
Note that the
actor = Squirrel true style syntax here is checking to see if
hasAcorn = true.
If these language features weren’t enough, I’d point out that F# code is much smaller in general than the equivalent C# code. Trending towards small units of code helps ensure a high degree of quality by making it harder for defects to hide.
I’ve said it before, but code you don’t have to write is code that won’t break and code you won’t need to maintain.
If you’d like to see a detailed comparison, my first F# project was to transform a small neural net library in C# to F#.
The F# Compiler is your Bodyguard
While there are many more awesome features of the F# language, these features I’ve highlighted here are enough to pique any manager’s curiosity about the quality and maintainability benefits in F#.
I’ll put it more succinctly: The appeal of F# is that it encourages software quality at the compiler level.
In the same vein, the F# compiler proves more about code correctness than the C# compiler does. This is because it checks a larger range of things and the language’s restrictions on mutability and null references catch more issues during compilation.
Put another way, you typically have a greater degree of confidence that a compiled F# program s free of errors than a compiled C# or VB.NET program.
Let’s take a look at some common concerns that people have when considering F#. These are concerns I myself had a year ago, so it’s only fitting I should speak to them now.
Any time you talk about adding a new language to your stack, there’s a chance that it won’t work out or it’s not fully ready for use.
F# was introduced in 2005 and the official support and integration for the language is only getting stronger as time goes on.
With Visual Studio 2019, it feels like F# has hit critical mass. This is a well-polished and thought out language that keeps getting better.
In fact, the innovations in F# and other languages steadily drip into the C# language as that language itself adds more support for functional logic. This was most recently evident with the release of C# 8.
Later in this article we’ll see how seamlessly F# integrates into existing .NET ecosystems.
The Learning Gap
The learning curve concern is a bit more justified than the prior, in my opinion, and why I was initially cautious of F#.
F# can be hard to learn – especially if you haven’t worked with a functional programming language before.
In fact, I rejected F# after reading an F# book, trying some simple experiments, and getting really frustrated and confused trying to do basic things with sequences of objects.
Honestly, I think I just read the wrong book.
To give F# another shot, I recently read Get Programming with F# by Isaac Abraham and it was exceptional.
This book is my recommended way to learn F# and I strongly recommend purchasing copies for your entire team if you’re considering the language.
I also recommend you avoid trying to re-implement some existing piece of code like I did as a way to learn F#.
If you look at F# and try to use the language to implement classes with inheritance, properties, members, and other common object-oriented language features, you’re going to have a bad time.
F# is at its best working with collections of objects to transform them, perform simple actions, and chaining together well-designed small functions to accomplish larger tasks.
Hiring F# Talent
As a manager, I’m cautious of adding a language that’s less popular to my technology stack.
F# is almost certainly always going to be less popular than C#. That means you’re going to have a smaller pool of people out there familiar with the language and you’ll likely need to pay them more given the specialized knowledge.
But here’s the thing – I don’t think you need to hire developers who are experts or even proficient with F#.
If you hire a developer who is experienced in .NET and understands the basics of functional programming, they’re going to be able to fight through and learn F# in a relatively short period of time.
Not only that, if you offer job candidates the opportunity to learn a new language on the job, they’re going to be eager to sign up. You’ll attract more people and, more importantly, you’ll attract the sort of people who want to continuously learn and grow.
While I’m on the topic, this is only based on personal observations, but I’d strongly suspect that it’d be easier to hire skilled .NET developers willing to learn F# than skilled VB .NET developers wanting to stay on VB .NET projects.
Functions at Scale
One of my greatest concerns with F# was the idea of organizing functions in a large project and whether the language would scale well for large pieces of work.
Let’s say you have 100 functions in a project. In an object-oriented programming mentality, you might split those up into 10 classes with 10 methods on average per class. Hiding logic inside of cohesive classes helps reduce the scope of global logic and helps you discover logic easier.
My initial fear with F# was that by having a large number of functions, it’d be hard to discover the function you truly need.
This turns out not to be an issue with F#, due primarily to two things:
- F# has the concept of modules, which contain types and functions. These allow you to organize related functions into a single module.
- F# allows you to declare functions as
privatemeaning they do not show up in the global scope. These functions can still be used by things inside of the current module.
These two techniques improve the ability to discover relevant functions and reduce noise, giving it the same degree of large scale project support as any other .NET application.
Integrating F# into Legacy .NET Projects
Okay, so F# is interesting, but I’ve got years of legacy code. How can I possibly replace it with code in a new language?
In short, you probably don’t replace anything.
Since F# compiles down to the Common Language Runtime (CLR), it can communicate with other .NET code, including code written in C# and VB .NET. Additionally, C# and VB .NET can call out to F# functions as pictured below:
The only restriction you have is that you can’t mix languages in a single project since an individual project goes through a language-specific compiler.
It’s perfectly valid to have a solution with F#, C#, and VB .NET projects all communicating with other projects in that solution as well as with NuGet packages.
Getting Started with F#
Since F# fits like a glove into the .NET ecosystem, if you were looking to evaluate it, you could consider a range of possibilities:
- Create a utility library in F#
- Write an F# console application
- Write a small WPF application in C# referencing F# logic
- Write a domain logic layer in F#
- Build an F# unit test project to test C# code (I personally wouldn’t, but it’s an option)
There are a lot of possibilities out there. Personally, I’d recommend starting with a utility library to work on collections of items. This is the lowest risk investment as if it doesn’t meet your needs you can quickly replace it with a C# library.
Note that the utility approach likely won’t use all of F#’s strengths at the domain modelling level, but it should give you enough of a taste into F#’s syntax and capabilities to inform future decisions.
While F# has a decent learning curve, the quality benefits it offers in terms of catching more issues at time of compilation and smaller code footprint are worth the investment.
I wish I could say that I adopted F# in my organization in early 2019 and here are the benefits it offered us. Sadly, I can’t because I got it wrong.
Don’t be like past Matt.
Tell your friends – F# is for real.