Mastering C#
March 24, 2023

Functional programming in C#

Image by Larisa Koshkina from Pixabay

Functional programming is a programming paradigm that emphasizes the use of functions as the primary building block for software development. While historically associated with languages such as Haskell and Lisp, functional programming is also becoming increasingly popular in mainstream programming languages such as C#. In this article, we'll explore what functional programming is and how it can be applied in C#.

Subscribe to my Telegram channel: C# Panda Cook Book
What is functional programming?

Functional programming is a programming paradigm that emphasizes the use of pure functions. Pure functions are functions that always return the same output for a given input and have no side effects. This means that pure functions don't modify any state outside of their own scope, which makes them easier to reason about and test.

In contrast, imperative programming, which is the dominant paradigm in C#, emphasizes the use of statements to change program state. Imperative programming often involves mutable state, which can make it harder to reason about the behavior of a program.

Functional programming also emphasizes the use of higher-order functions, which are functions that take other functions as arguments or return functions as their result. This allows for the creation of more modular and composable code, which can lead to increased code reuse and maintainability.

Functional programming in C#, also known as C# Pure Functions

While C# is primarily an imperative language, it does have features that make it possible to write functional code. Some of the key features of C# that enable functional programming include:

  1. Anonymous functions and lambda expressions: C# supports the creation of anonymous functions and lambda expressions, which are a concise way to create functions without the need for a named method. This makes it easy to pass functions as arguments to other functions, which is a key feature of functional programming.
  2. Higher-order functions: C# supports the creation of higher-order functions, which are functions that take other functions as arguments or return functions as their result. This allows for the creation of more modular and composable code.
  3. Immutable data structures: C# supports the creation of immutable data structures, which are data structures that cannot be modified once they are created. This makes it easier to reason about the behavior of a program and can help prevent bugs related to mutable state.
  4. LINQ: C# includes LINQ (Language-Integrated Query), which is a set of language extensions that allow for functional-style queries on collections of objects. This makes it easier to write code that is both concise and expressive.

Does C# qualify as a functional language? I think, yes. With each new release of the C# language, we have seen a proliferation of functional features in C#.

For instance, C# 2.0 introduced the ability to pass and return functions as values for higher-order functions via delegates, and supported anonymous delegates to a limited extent. In C# 3.0 and 3.5, anonymous functions were improved to allow for true closures.

Subsequently, C#’s LINQ functionality can be viewed as a unique take on Haskell's list comprehensions. Haskell is a statically-typed, purely functional programming language that utilizes type inference and lazy evaluation, which are somewhat similar to the type inference capabilities of C# and LINQ.

Additionally, anonymous types in C# bear a resemblance to ML records, a feature of ML, a general-purpose functional programming language.

Benefits of functional programming in C#

There are several benefits to using functional programming in C#, including:

  1. Improved code maintainability: Functional programming emphasizes modularity and composability, which can lead to code that is easier to maintain and modify over time.
  2. Improved code reuse: Functional programming emphasizes the use of higher-order functions and immutable data structures, which can lead to code that is more reusable.
  3. Improved code safety: Functional programming emphasizes the use of pure functions, which are easier to reason about and test. This can lead to code that is less error-prone and more reliable.
  4. Improved performance: Functional programming can lead to code that is more efficient, as it often involves the use of immutable data structures and lazy evaluation.

Code samples

Here are some code samples that demonstrate functional programming techniques in C#.

Anonymous functions and lambda expressions:

// Using an anonymous function to filter a list of numbers
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(delegate (int n) { return n % 2 == 0; });
// Using a lambda expression to calculate the sum of a list of numbers
var sum = numbers.Aggregate((acc, n) => acc + n);

Higher-order functions (read below about higher-order functions):

// Using a higher-order function to filter a list of strings
static Func<string, bool> FilterStartsWith(string prefix) =>
    s => s.StartsWith(prefix);
    
var names = new List<string> { "Alice", "Bob", "Charlie" };
var filteredNames = names.Where(FilterStartsWith("B"));

Immutable data structures:

// Creating an immutable list of strings
var immutableList = ImmutableList.Create("Alice", "Bob", "Charlie");
// Adding a new string to the immutable list returns a new list
var newList = immutableList.Add("Dave");

LINQ:

// Using LINQ to calculate the sum of a list of numbers
var sum = numbers.Sum();
// Using LINQ to filter a list of strings
var filteredNames = names.Where(n => n.Length > 4);
Real-life example 1

A real-life example that demonstrates how functional programming can be used to manipulate data in C#: this example calculates the average temperature in a list of daily temperature readings.

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        var temperatureReadings = new List<double>
        {
            23.5, 24.3, 26.1, 25.7, 22.8, 27.3, 28.5
        };

        // Using LINQ to calculate the average temperature
        var averageTemperature = temperatureReadings.Average();

        Console.WriteLine("The average temperature:");
        Console.WriteLine(quot;{averageTemperature:F1}ºC.");
    }
}

In this example, we have a list of daily temperature readings, which are represented as double values. We use LINQ's Average method to calculate the average temperature, which returns a single double value. We then print the result to the console.

This example demonstrates how functional programming techniques, such as the use of higher-order functions and immutable data structures, can be used to create concise and expressive code that is easy to reason about and maintain. By using LINQ, we are able to write code that is both efficient and easy to understand.

Real-life example 2

Another real-life example that demonstrates how functional programming can be used to manipulate data in C#. This example calculates the factorial of a given number using a recursive function.

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.Write("Enter a number: ");
        int number = int.Parse(Console.ReadLine());

        int factorial = CalculateFactorial(number);

        Console.WriteLine(quot;The factorial of {number} is {factorial}.");
    }

    static int CalculateFactorial(int number)
    {
        if (number == 0)
        {
            return 1;
        }
        else
        {
            return number * CalculateFactorial(number - 1);
        }
    }
}

In this example, we ask the user to enter a number, which we then pass to the CalculateFactorial function. This function uses recursion to calculate the factorial of the given number. If the number is 0, the function returns 1 (since the factorial of 0 is 1). Otherwise, it multiplies the number by the factorial of the number minus 1.This example demonstrates how functional programming techniques, such as recursion, can be used to solve complex problems in a simple and elegant way. By breaking the problem down into smaller subproblems, we can write code that is easy to understand and maintain.

Higher-order functions

Higher-order functions are functions that take one or more functions as arguments, and/or return functions as results. They are a fundamental concept in functional programming and provide a powerful way to abstract and compose code.

In C#, a higher-order function is simply a function that takes a delegate or a lambda expression as a parameter or returns a delegate or a lambda expression as a result. Here's an example that demonstrates how to define and use higher-order functions in C#:

using System;

class Program
{
    static void Main(string[] args)
    {
        Func<int, int> square = x => x * x;
        Func<int, int> doubleSquare = Compose(square, square);

        Console.WriteLine(square(3)); // Output: 9
        Console.WriteLine(doubleSquare(3)); // Output: 81
    }

    static Func<T, T> Compose<T>(Func<T, T> f, Func<T, T> g)
    {
        return x => g(f(x));
    }
}

In this example, we define a higher-order function called Compose, which takes two functions f and g as arguments and returns a new function that is the composition of f and g. The Compose function works by taking an input value x, applying f to it, and then applying g to the result of f(x).

We then define two functions square and doubleSquare, which are both of type Func<int, int>. Then square simply squares its input value, while doubleSquare is the composition of square and square.

Finally, we call square and doubleSquare with an input value of 3 and print the results to the console. The output shows that square returns 9, while doubleSquare returns 81.

Higher-order functions are useful for abstracting and composing code. They allow you to write generic and reusable code that can be applied to different types and contexts. Higher-order functions are also useful for implementing common patterns such as map, filter, and reduce, which are widely used in functional programming.

Did you know, Functional programmers never die, they just return. 😉

This joke is a lighthearted play on the unique features of functional programming. This is a pun on the fact that in functional programming, functions often return values rather than changing state or producing side effects. So, when a function has completed its task, it simply returns a value and goes out of scope. This is in contrast to imperative programming, where functions may change the state of the program or produce side effects that persist beyond the function's scope. The joke also implies that functional programmers have a special ability to gracefully exit the program when their task is done.

Conclusion

While C# is primarily an imperative language, it does have features that make it possible to write functional code. Functional programming can lead to code that is more modular, composable, and maintainable, while also being more efficient and reliable. By embracing functional programming techniques, C# developers can create code that is easier to reason about and test, and that can be more easily modified and extended over time.

Subscribe to my Telegram channel: C# Panda Cook Book