Embracing functional programming in C# - Part 4

In this post, we will briefly explore how to harness functional programming to circumvent state mutation.

How can we steer clear of state mutation ? I require the ability to alter the state of my variables !

Yes, of course; otherwise, developers would be rather useless, and programs would not have much value. In fact, avoiding mutation is not the right directive. Instead, what we should aim for is avoiding in-place mutation. To clarify, if we want to modify a variable in an object, we should recreate the entire object with the new value for that variable.

Consider the following code.

 1public class Customer
 2{
 3    public string Name { get; set; }
 4    public int Age { get; set; }
 5	
 6    public Customer()
 7    {
 8    }
 9
10    public void SetAge(int age)
11    {
12        Age = age;
13    }
14}

This class could be present in numerous commercial applications, and there is nothing inherently wrong with it except that the state can be mutated in place. We can directly modify the name and the age. We delved into how this practice could lead to severe bugs at the beginning of this series.

The issue lies in the mutability of Age and Name. In functional programming, variables are inherently immutable, but in C#, we need to employ alternative methods to ensure immutability.

How can we make these variables immutable ?

As mentioned earlier, we have to recreate the object every time we want to modify one of its members.

 1public class Customer
 2{
 3    public string Name { get; }
 4    public int Age { get; }
 5	
 6    public Customer(string name, int age)
 7    {
 8        Name = name;
 9        Age = age;
10    }
11
12    public Customer WithAge(int age)
13    {
14        return new Customer(Name, age);
15    }
16}
  • We modified the constructor to take explicit arguments.
  • We made the properties read-only (no setters).
  • We introduced a WithAge method that returns a customer.
Information

This approach is quite general, but it must be acknowledged that it could become cumbersome if we have dozens of properties. Functional Programming in C# (Buonanno) provides some means to circumvent this phenomenon.

Wait a minute ! Isn't it terribly inefficient ?

Indeed, every time a variable is modified, a new object is created, which can put pressure on the garbage collector, especially for large objects. Anyway, this is one of the reasons imperative code with state mutation was widely adopted at the beginning of computer science: memory was scarce and expensive, so it had to be used with moderation.

But significant advancements have been made in the last few decades:

  • Memory is less of a problem today, and garbage collectors have been greatly optimized.
  • In the realm of memory allocation internals, particularly within the context of C#, there exist mechanisms designed to mitigate the necessity of recreating the entire object, exemplified by techniques like shallow copying.

The memory overhead is often imperceptible, and we recommend adopting this approach to prevent any disturbances during nighttime.

Important

C# 9 introduced records as a language feature, which aligns with the functional programming paradigm by allowing the creation of immutable data structures more conveniently. Records in C# provide a concise syntax for declaring immutable types, and they come with built-in features like value-based equality, making them well-suited for scenarios where immutability is desired, such as functional programming practices.

This addition in C# reflects a continued effort to incorporate functional programming concepts and enhance support for immutability in the language.

In the final post, we will explore how functional programming can elevate concurrency by leveraging the principles of immutability and purity.

Embracing functional programming in C# - Part 5