4 min read
·
By Runar Ovesen Hjerpbakk
·
December 5, 2020
I really love C#. It's by far my favorite programming language. And who wouldn't agree? I mean with a piffy, roll of the tounge Wikipedia description like this, what is not to love?
"C# is a general-purpose, multi-paradigm programming language encompassing static typing, strong typing, lexically scoped, imperative, declarative, functional, generic, object-oriented (class-based), and component-oriented programming disciplines."
"Yeah, yeah", I hear you say dear reader. "I know C# is great to use if you want to write better Java apps, even so, this is the Bekk Functional advent calendar. What are you rambling about?"
I know you love your pure, scalable functions in Haskell and your fancy web pages written in Elm, but did you spot the magic word in the description above? It said:
"... functional ..."
Yes! As the late Sean Connery said so excitedly:
C# is not only useful on the backend, while writing iOS and Android apps, replacing JS in the browser or as a scripting language, it also becomes more and more functional-friendly with every new language version!
However, what does it mean to be a functional-friendly programming language?
Enrico Buonanno argues in his book Functional Programming in C#: How to write better C# code that we should care about functional programming because it gives us the following:
C# supports a lot of functional building blocks, such as function delegates, higher order functions, expressions instead of statements, method chaining, extension methods, yield, LINQ, tuples and local functions. Despite all that, the core functional tenet of immutability of data has always been a C# pain point. Until now.
C# second greatest error[^error] was to one-up Java and introduce the concept of properties as a first class language feature with the poorest defaults of all time: the default property syntax made it all too easy to create classes with mutable properties. The deed is as simple as:
public class Person
{
public uint Age { get; set; }
}
Alas, premature aging can take its toll and we should've listened to the dog, hound, wolf or whatever, and make the age
property immutable. It came with a cost however, amounting to a lot more work and boilerplate code. I mean, I got bored by just writing out this small example from the past:
public class Person
{
private readonly uint age;
public Person(uint age)
{
this.age = age;
}
public uint Age { return age; }
}
C# 6 made the pit of success a bit deeper by introducing read-only properties using only a get
accessor:
public class Person
{
public Person(uint age)
{
Age = age;
}
public uint Age { get; }
}
C# 7.2 made declaring our intent more explicit by allowing a struct
to be marked as readonly
:
public readonly struct Person
{
public Person(uint age)
{
Age = age;
}
public uint Age { get; }
}
This is all well and good, yet consider when our class or struct has multiple readonly properties and we need to construct a new copy with an updated value in one or more of them. My head hurts just by thinking about it. The boilerplate has tested my Christmas spirit, why must we endure this error prone, manual waste? A better way must exist!
And finally this year, at the end of the worst year ever, it does! Microsoft bestowed .Net 5 and C# 9 upon us, giving us my most requested feature: language support for immutable data types, Record types!
public record Person(uint Age);
// No, really, this the complete example!
A record
is immutable in that none of the properties can be modified once it's been created. When we define a record type, the compiler provides several useful methods for us:
GetHashCode()
PrintMembers
and ToString()
Seems like we're finally winning the war against boilerplate, and if we need to create a copy with an updated value, we can now easily use the with
keyword:
var me = new Person(37);
Person meAYearOlder = me with { Age = 38 };
With such quality functional features in C# like immutable records, not even time's inevitable flowing towards my 40th birthday and usage of 24(!) year old memes can break this aging developer's Christmas spirit!
[^error]: C#'s greatest error was to introduce the concept of null and not fixing it until C# 8.