Author: Vladimir Khorikov www.eastbanctech.com
With the advent of LINQ, C# has gotten a significant exposure to functional programming. However, functional programming in C# is not restricted to the use of extension methods, lambdas and immutable classes. There are a lot of practices that haven't been adopted as widely because there's not much of native language support for them in C#. Still, they can be extremely beneficial should you incorporate them into your day-to-day work.
From this presentation you’ll learn:
• The fundamental principles behind functional programming,
• Why they are important,
• How to apply them in practice.
6. What is Functional Programming?
f
Referential transparency:
same input – same result
Information about possible
inputs and outcomes
7. What is Functional Programming?
public double Calculate(double x, double y)
{
return x * x + y * y;
}
public long TicksElapsedFrom(int year)
{
DateTime now = DateTime.Now;
DateTime then = new DateTime(year, 1, 1);
return (now - then).Ticks;
}
Same input – same result Result is always different
8. What is Functional Programming?
public static int Divide(int x, int y)
{
return x / y;
}
f
Integer
Integer
Integer
1
0
?
DivideByZeroException
10. Method Signature Honesty
Honest signatureDishonest signature
public static int Divide(int x, int y)
{
return x / y;
}
public static int Divide(int x, NonZeroInteger y)
{
return x / y.Value;
}
public static int? Divide(int x, int y)
{
if (y == 0)
return null;
return x / y;
}
11. Mathematical Function
• Honest
• Has precisely defined input and output
• Referentially transparent
• Doesn’t affect or refer to the global state
17. Why Does Immutability Matter?
• Increased readability
• A single place for validating invariants
• Automatic thread safety
18. How to Deal with Side Effects
Command–query separation principle
Command Query
Produces side effects Side-effect free
Returns void Returns non-void
19. How to Deal with Side Effects
public class CustomerService {
public void Process(string customerName, string addressString) {
Address address = CreateAddress(addressString);
Customer customer = CreateCustomer(customerName, address);
SaveCustomer(customer);
}
private Address CreateAddress(string addressString) {
return new Address(addressString);
}
private Customer CreateCustomer(string name, Address address) {
return new Customer(name, address);
}
private void SaveCustomer(Customer customer) {
var repository = new Repository();
repository.Save(customer);
}
}
Command
Query
Command
Query
20. How to Deal with Side Effects
var stack = new Stack<string>();
stack.Push("value"); // Command
string value = stack.Pop(); // Both query and command
21. How to Deal with Side Effects
Application
Domain logic Mutating state
Generates artifacts
Uses artifacts to change
the system’s state
22. How to Deal with Side Effects
Immutable CoreInput Artifacts
Mutable Shell
23. Exceptions and Readability
public ActionResult CreateEmployee(string name) {
try {
ValidateName(name);
// Rest of the method
return View("Success");
}
catch (ValidationException ex) {
return View("Error", ex.Message);
}
}
private void ValidateName(string name) {
if (string.IsNullOrWhiteSpace(name))
throw new ValidationException("Name cannot be empty");
if (name.Length > 100)
throw new ValidationException("Name is too long");
}
29. Use Cases for Exceptions
• Exceptions are for exceptional situations
• Exceptions should signalize a bug
• Don’t use exceptions in situations you expect to happen
30. Use Cases for Exceptions
Validations
Exceptional
situation=
32. Drawbacks of Primitive Obsession
public class User
{
public string Email { get; }
public User(string email)
{
Email = email;
}
}
33. public class User
{
public string Email { get; }
public User(string email)
{
if (string.IsNullOrWhiteSpace(email))
throw new ArgumentException("Email should not be empty");
email = email.Trim();
if (email.Length > 256)
throw new ArgumentException("Email is too long");
if (!email.Contains("@"))
throw new ArgumentException("Email is invalid");
Email = email;
}
}
Drawbacks of Primitive Obsession
34. Drawbacks of Primitive Obsession
public class Organization
{
public string PrimaryEmail { get; }
public Organization(string primaryEmail)
{
PrimaryEmail = primaryEmail;
}
}
35. public class Organization
{
public string PrimaryEmail { get; }
public Organization(string primaryEmail)
{
if (string.IsNullOrWhiteSpace(primaryEmail))
throw new ArgumentException("Email should not be empty");
primaryEmail = primaryEmail.Trim();
if (primaryEmail.Length > 256)
throw new ArgumentException("Email is too long");
if (!primaryEmail.Contains("@"))
throw new ArgumentException("Email is invalid");
PrimaryEmail = primaryEmail;
}
}
Drawbacks of Primitive Obsession
36. Drawbacks of Primitive Obsession
Dishonest signature
public class UserFactory
{
public User CreateUser(string email)
{
return new User(email);
}
}
public int Divide(int x, int y)
{
return x / y;
}
fstring user
Dishonest signature
39. Drawbacks of Primitive Obsession
public class UserFactory
{
public User CreateUser(Email email)
{
return new User(email);
}
}
public int Divide(int x, NonZeroInteger y)
{
return x / y;
}
femail user
Honest signature Honest signature
40. Getting Rid of Primitive Obsession
• Removing duplications
• Method signature honesty
• Stronger type system
42. The Billion-dollar Mistake
“I call it my billion-dollar mistake. It has caused
a billion dollars of pain and damage in the last
forty years.”
Tony Hoare
43. The Billion-dollar Mistake
public class Organization
{
public Employee GetEmployee(string name)
{
/* ... */
}
}
public class OrganizationRepository
{
public Organization GetById(int id)
{
/* ... */
}
}
44. The Billion-dollar Mistake
public class MyClassOrNull
{
// either null
public readonly Null Null;
// or actually a MyClass instance
public readonly MyClass MyClass;
}
public class MyClass
{
}
47. Mitigating the Billion-dollar Mistake
public class OrganizationRepository
{
public Organization GetById(int id)
{
/* ... */
}
}
Maybe<Organization>
48. Mitigating the Billion-dollar Mistake
public class OrganizationRepository
{
public Maybe<Organization> GetById(int id)
{
/* ... */
}
}
public class Organization
{
public Employee GetEmployee(string name)
{
/* ... */
}
}
49. Mitigating the Billion-dollar Mistake
public class OrganizationRepository
{
public Maybe<Organization> GetById(int id)
{
/* ... */
}
}
public static int? Divide(int x, int y)
{
if (y == 0)
return null;
return x / y;
}
Honest
Honest
51. Summary
• Functional programming is programming with mathematical functions
• Method signature honesty
• Referential transparency
• Side effects and exceptions make your code dishonest about the
outcome it may produce
• Primitive obsession makes your code dishonest about its input parts
• Nulls make your code dishonest about both its inputs and outputs
• Applying Functional Principles in C# Pluralsight course:
https://app.pluralsight.com/courses/csharp-applying-functional-
principles
Composable: can be treated in isolation
Easier to reason about, no need to fall down to implementation details
Easier to unit test, just provide input and verify output, no mocks
Reducing complexity: fewer bugs, better maintainability
The principles – referential transparency and method signature honesty - look quite simple, but if applied in practice, have interesting consequences. Let’s see what those consequences are.
When we talked about the concept of method signature honesty in the previous module, we discussed that whenever you define a method, you should try to put into its signature the information about all its possible outcomes. By defining a method with a side-effect, we loose this information.
The signature of such a method no longer tells us what the actual result of the operation is.
Hinders our ability to reason about the code.
So, how to fix that?
Lift all possible outcomes to a signature level.
- Immutability does exactly this: it forces you to be honest about what the method does.