Functional Programming - part 5
Obsession with Primitive Types
Continuing the series of articles related to functional programming, today I want to examine whether or not we should use primitive types in our code.
If you haven’t read the previous parts, I recommend starting with the following:
- Part 1: Introduction to Concepts
- Part 2: Practical Examples
- Part 3: Immutability
- Part 4: Dealing with Exceptions
In domain modeling, we often use primitive types like int
, string
, and so on. In fact, we can say that we have an obsession with these primitive data types.
Consider the following code snippet:
1
2
3
4
5
6
public class UserFactory
{
public User CreateUser(string email) {
return new User(email);
}
}
The UserFactory
class has a method called CreateUser that takes a string (an email) as input and returns a User
object. So, what’s the issue with this method?
If you recall from previous parts, we talked about the concept of “Honesty”. Simply put, we should be able to understand the function’s purpose and output just from its signature.
This method is not honest. The function signature does not indicate that the string could be invalid, empty, have an incorrect length, and so on. We can’t infer these possible issues from just the method signature.
To make this clearer, keep the example above in mind. In the Divide
function, which performs division, the parameter y
(of type int
) could be zero, causing an exception. Since the method returns an int
, we don’t expect it to throw an exception
. We’ve already discussed exceptions in detail in the previous part.
Now, imagine you want to use multiple emails as inputs instead of just one. Should you repeat the validation logic for each input parameter?
Using primitive types recklessly can lead to losing the honesty of our methods and violating the **DRY (Don’t Repeat Yourself) ** principle.
The discussion about when to use or not use primitive types has many angles. One such approach is discussed in Domain-Driven Design (DDD) under the concept of Value Objects. The goal of this section is simply to touch on one aspect of this issue, but for further reading and more in-depth information, you can refer to the book Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans.
What Should We Use Instead of Primitive Types?
The answer is quite simple: You need to create a dedicated type. For example, instead of using a string for email, you can create a class called Email
that has a property named Value.
There are various ways to achieve this, but my suggestion is to use the following method:
In this approach, we create a class as a Value Object
, which encapsulates the primitive type we are working with. The class implements logic for comparison and operators like ==
and !=
through .Equals
and .GetHashCode()
.
For instance, for the Email
class, we can implement it as follows:
1
public class EmailAddress : ValueOf<string, EmailAddress> { }
You can instantiate this class like so:
1
EmailAddress emailAddress = EmailAddress.From("[email protected]");
For more complex examples, such as an address that includes a street, postcode, etc., you can use C#’s Tuple
feature (introduced in C# 7) like this:
1
public class Address : ValueOf<(string firstLine, string secondLine, Postcode postcode), Address> { }
Finally, for implementing validation logic, you can override the Validate method and avoid violating the DRY principle.
The method outlined in this article is simply to familiarize you with cleaner code using functional programming concepts. In real-world applications, you’ll likely face issues with storing these objects or working with libraries like Entity Framework, but these are straightforward challenges to resolve.
If you encounter any issues during implementation, feel free to leave a comment under this post or on the gist.