While F# is a functional language that can be used in an imperative and object-oriented style, C# is often claimed to work the other way around.
Published on Thu, September 26, 2019
Note: As of June 2023, this article is highly outdated. C# catched up on many of the (missing) features mentioned here. The post is kept online for historical reasons.
Both F# and C# had some influence on each other over the years. While the work on generics lead to the creation of F#, things like ”var” (C# 3), ”async/await” (C# 5), tuples, pattern matching (C# 7), and non-null pointers (C# 8) have been heavily influenced by F#. C# developers are also used to other functional techniques, especially LINQ with its heavy usage of lambda expressions, and extension methods.
C# supports various functional aspects so that it may be worth to take a glance at the previously introduced core fundamentals of functional programming from its perspective.
The .NET BCL offers a set of immutable collections. Beyond that, most things are mutable by default in C#, except for simple value types and strings. While it is possible to design, for example, classes to have read-only members only, a built-in copy and update mechanism is missing.
public class User
{
public string FirstName { get; }
public string LastName { get; }
public User(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public User WithFirstName(string firstName) => new User(firstName, LastName);
public User WithLastName(string lastName) => new User(FirstName, lastName);
}
var user = new User("Jane", "Doe"); // Jane Doe
user = user.WithFirstName("Janet"); // Janet Doe
The sample shows one simple way on how to implement copy and update manually. However, this will only last until a type is used that is not under control by the developer. Because for third-party types immutability cannot be ensured. But there are, in fact, alternative approaches. Oliver Sturm, for example, proposes a technique he calls automatic cloning. And it is also possible to serialize an object and to deserialize it to retrieve a copy and avoid modifying the original.
Compared to F#, where almost everything is an expression, the case for C# is fundamentally different. Usually, a lot of statements dominate the picture, leading to an imperative style applied by most developers. However, it is possible to use expressions over statements for a lot of cases in C#, too.
int Add(int x, int y) => x + y;
int z = Add(1, 2);
bool equals1 = z == 3; // -> true
bool equals2 = z == z; // -> true
bool equals3 = z == Add(1, 2); // -> true
bool equals4 = Add(1, 2) == z; // -> true
Functions are first-class citizens in C# as well as in F#. It is possible to pass along static functions, class members, or even local functions:
string FirstName(User u) => u.FirstName;
string FullName(User u) => $"{u.FirstName} {u.LastName}";
string SayHello(Func<User, string> formatter, User u)
=> $"Hello, {formatter(u)}!";
var user = new User("Jane", "Doe");
SayHello(FirstName, user); // Hello, Jane!
SayHello(FullName, user); // Hello, Jane Doe!
In terms of purity, C# offers the same (dis)comfort as F#. Pure functions can be used, but there is no way to actually enforce purity on them. So it is the developer's job to take care of it.
As shown, C# supports some important functional concepts. In direct comparison to F#, however, it suffers, from some limitations at type inference, the absence of data types like discriminated unions, and especially much boilerplate code that has to be written in order to achieve immutability and to enable structural equality comparisons. Enrico Buonanno even suggests C# developers to consider to define their types in F# rather than C# in order to get rid of those limitations (which indeed can be a good idea).