Why Do We Like Functional Programming?

2024-11-11

Why do we like functional programming so much? The simple answer is that it makes the day-to-day life of developers easier—in this article we will explain how.

If you think about it, most programs model business rules. Here are some typical examples:

Often these rules can be encoded in the type system of the language. For example:

This frees the programmer from having to think about preventing these errors, because the type system makes sure they can’t happen! (This is often referred to as making illegal states unrepresentable)

However, there’s a problem with this approach, at least in mainstream OOP languages: In Java or C# creating a new class is a lot of work! You have to define methods for construction, equality, stringification, serialization, and so on. Sure, you can have your IDE generate the code, but you still have to manage all that boilerplate.

For example, here’s how we might write a very simple representation of an email address in C#:

public class EmailAddress
{
    public string Address { get; }

    public EmailAddress(string address)
    {
        if (address == null)
        {
            throw new ArgumentException("Email address cannot be null.");
        }
        Address = address;
    }

    public override bool Equals(object obj)
    {
        if (obj is EmailAddress other)
        {
            return string.Equals(Address, other.Address);
        }
        return false;
    }

    public override int GetHashCode()
    {
        return Address.GetHashCode();
    }

    public override string ToString()
    {
        return Address;
    }
}

(Implementing email address validation is left as an exercise to the reader)

The programmer is stuck in an unenviable position: Either put in the hard work to encode the business rules in the type system, or fall back to using primitive types and ensuring correctness by hand; probably with the help of a lot of unit tests.

But why is all this boilerplate needed? The core problem is that an object is supposed to encapsulate state; the outside world should not know (nor need to know!) what the insides of the object looks like. However, this also means that the outside world cannot draw any conclusions about an object or a class. For example, what does it mean for two Administrator values to be equal to each other? Are they equal only if they reference the same object, or are they equal if they reference the same user? The world outside the object can’t tell, and so it falls to the object (and by extension, the programmer) to create a definition of equality for each class.

Functional programming does away with all that; just like in mathematics, a number is a number and a string is a string. If they have the same value, they are, for all intents and purposes, the same. This also means that equality, stringification, comparison etc become trivial; if a value is just a value, we can reuse the same logic for all instances of that value. This also means that creating new types become a trivial exercise. For comparison, here’s the canonical definition of an email address in F#:

type EmailAddress = EmailAddress of string

If something is easier, people tend to do it more often. Functional programming makes it easy to encode domain rules in the type system, so programmers are nudged to do it more. In the end, this creates software that is more correct, easier to maintain and more fun to write.