but... ¿why I should learn functional programming?
You could read this post in Spanish here.
For your professional life, it is very good that you handle at least three types of languages
- Object Oriented (c #, java)
- Dynamic (javascript, python)
- Functional (scala, f #, Haskell)
Because behind the language itself, is the way to solve the problem. And that's what costs the most about functional programming. I'm not talking about one being superior to the other, but that they are two different paradigms and it's good that you have them in your mental toolbox.
What do you need to know to continue reading?
- Be familiar with object-oriented programming's (hereinafter OOP) concepts like class, objects, polymorphism, and inheritance
- Must read C#
What do you not need?
- Be an expert on OOP
- Being a ninja of C #
- Know something of Haskell, Scala, F # and other functional languages.
Why do I have to trust in maths?
In Wikipedia, if you are looking for functional programming it says
In computer science, functional programming is a paradigm of declarative programming based on the use of mathematical functions ...
What do maths have that will help me become a better programmer? Why do I have to trust them when they almost ruin my teenage years? :)
Let's start with a simple example. A function that returns the Value Added Tax (hereinafter VAT) for a given amount.
First, we define the function, in maths, it will be VAT(amount, percentageVAT) = amount * (percentageVAT / 100)
whereas in C # it will be
public decimal GetVat(double amount, double percentageVAT)
{
return (amount * (percentageVAT/100));
}
Now we call the function defined with the value 100 and 21
Doing maths: VAT(100, 21)
and in C # var result = GetVAT (100, 21);
From this example, we are able to see 3 things in common that maths and this function of C # have
- They are only dedicated to calculating the VAT.
- The only dependence they have are the input parameters.
- For a set of input parameters, only one output value corresponds to those. No matter how many times I put 100 and 21, I'll always get back 21.
First: The S of solid.
Doing maths, adding only adds. You can combine different formulas, but each one is dedicated to his own, it has only a responsibility, that is to say, they are Single responsibility, the S from SOLID
Now let me put a typical object-oriented programming function, add an invoice detail to an invoice.
void AddLine(InvoiceLine newInvoiceLine)
{
this.lines.Add(newInvoiceLine); // 1
this.totalPrice += newInvoiceLine.Total; //2
}
One of the properties of OOP is encapsulation, and this function acts very well because it hides what happens after adding. But as you have noticed, it does two tasks, 1 - add an item and 2 - recalculate the total.
The first thing you think about is to separate it into two methods
void AddLine(InvoiceLine newInvoiceLine)
{
this.lines.add(newInvoiceLine);
}
void UpdateTotal(InvoiceLine newInvoiceLine)
{
this.totalPrice += newInvoiceLine.Total;
}
But the problem with this solution is that you generate a dependency between the functions, forcing to place the second call after the first one so that the internal state remains coherent. The real problem is... total is a materialized result, we need to change to a functional result.
decimal GetTotal()
{
decimal total = 0;
foreach(var line in this.lines)
{
total += line.Total;
}
return total;
}
Now we can remove the totalPrice
variable because it is being replaced by the GetTotal function.
We can make the GetTotal function a little different, so it has a more functional style without using the total internal variable, to do this we will use Linq.
decimal GetTotal()
{
return this.details.Sum(item => item.Price);
}
Even better, as a read-only property
public decimal Total
{
get { return Details.Sum(l => l.Total); }
}
And now, you can write the first functional programming's lesson. When refactoring to functional programming, the internal state of objects is reduced.
Now we have the first completed objective. Each function has a unique responsibility.
Second: No side effects.
When a function causes a change to something out of its range, it is called a side effect. Because of this, the function has to receive everything necessary to be able to complete its responsibility by means of parameters.
Continuing with the example of the invoice, imagine that you have a method named cancel. The moment you cancel the invoice you update the balance of the customer.
private Customer customer;
public void Cancel()
{
this.state = StateKind.canceled;
this.customer.UpdateBalance(this.GetTotal());
}
Now let's put a scenario where the customer has a balance of
customer.Balance; // 500€
invoice.Cancel();
customer.Balance // 600€
This is a clear example of a side effect and a function that is not mathematical because it does not always return the same result. How can I do so that Cancel becomes a no-side-effect?
Let's return again to the subject of responsibility. It is not the responsibility of cancelling the invoice to update the customer's balance.
public void Cancel()
{
this.state = StateKind.canceled;
}
The Customer class will be in charge of receiving an invoice to cancel and thus update your balance. After verifying that the invoice is cancelled, it will adjust the balance.
public class Customer
{
void CancelInvoice(Invoice invoice)
{
if (invoice.State == StateKind.canceled;)
{
this.UpdateBalance(invoice.Total);
}
}
}
With this design, we have two good news.
1. The Cancel method no longer needs the customer class. Maybe it is needed on another part of the invoice class, but no longer on this method. This makes the Bounded context of the customer object for the billing system more limited. And maybe we will need an integration event to notify Customer bounded context.
2. In the customer class, the UpdateBalance method can now be a private method.
Returning to the previous example we now have
customer.Balance; // 500€
invoice.Cancel();
customer.Balance; // still is 500€
customer.CancelInvoice(invoice);
customer.Balance; //now is 600€
We have moved the responsibility to the customer so it makes two calls sequentially.
Although this design is better, it is not 100% functional, because the Cancel method modifies a variable (state) that is not received as a parameter.
Third: pure functions or referential transparency
In maths, all functions are referentially transparent. The cosine of a 90-degree angle remains 0, no matter how many years have passed and how many times you repeat it, it will always be Cos (90) = 0
. No matter how you pass the reference Cos (89 + 1) or Cos (91- 1), it will be the same result.
Let's go back to the AddInvoice function. Although it is perfectly valid in object-oriented programming, in terms of referential transparency it is not, because it changes the internal state when adding an item to the list.
void AddLine(InvoiceLine newInvoiceLine)
{
this.lines.add(newInvoiceLine);
}
If we want this function to be a pure and non-side-effect one we need to changes same things:
1 - Expose the properties of the invoice object by the constructor. We going to pass the new state to a new instance.
2 - The internal list of lines change from List to InmutableList. In this way, when we change the collection returns a new immutable list instance.
3 - Create a local function to manage changes in the collection and return an invoice or the same object if we don't have changes.
4 - Adding the item to the immutable collection, and use the previous function.
5 - Change the 'void' by Invoice.
Let's do it. First step. In the future steps, we need to create another invoice instance and pass the new state. One note, When I speak of the state of an object I mean the value of all properties, not just the state property. I'm using one IEnumerable, in this way call this constructor is easier. Inside, I convert it to immutable.
public Invoice(IEnumerable<InvoiceLine> lines, StateKind state)
{
this.lines = lines.ToImmutableList();
State = state;
}
The second step. Change the internal variable as ImmutableList, but keep the public as an IEnumerable.
private ImmutableList<InvoiceLine> lines;
public IEnumerable<InvoiceLine> Lines
{
get { return lines; }
}
Third step. Local function. If the immutable instance is different, then create a new Invoice. The property state doesn't change, we create the new invoice instance with the same value we have right now.
private Invoice WithLines(IEnumerable<InvoiceLine> value)
{
return Object.ReferenceEquals(lines, value)
? this
: new Invoice(value, State);
}
Steps 4th and 5th. When we add an item to the immutable list returns a new instance of an immutable collection, and pass it to the before function.
public Invoice AddLine(InvoiceLine newOrderLine)
{
return WithLines(lines.Add(newOrderLine));
}
We have the internal function, then we can add remove line functionality.
public Invoice RemoveLine(InvoiceLine invoiceLine)
{
return WithLines(lines.Remove(invoiceLine));
}
What happend with the property State in the Cancel method?
Ok, we change it.
public Invoice Cancel()
{
return (State == StateKind.canceled)
? this
: new Invoice(Lines, StateKind.canceled);
}
Second note, when you try to make functional transparency the immutability of the data as goes without saying. That is, immutability is a consequence of the referential transparency that is used in maths.
Ok, right now the invoice class is immutable, all the property are read-only and any change returns a new Invoice instance.
Now, we are going to do the same in Customer class. Change the constructor, the properties to read-only and the method return a new instance.
public class Customer
{
public Customer(decimal balance)
{
this.Balance = balance;
}
public decimal Balance { get; private set; }
public Customer CancelInvoice(Invoice invoice)
{
return (invoice.State == StateKind.canceled)
? AddToBalance(invoice.Total)
: this;
}
private Customer AddToBalance(decimal total)
{
return new Customer(Balance + toTake);
}
}
Now let's see how these functions are called. We will start assuming that the detail list has 2 items, one of 5 € and another of
List<InvoiceLine> lines = new List<InvoiceLine>
{
new InvoiceLine(1, 5),
new InvoiceLine(1, 15)
};
Invoice invoice = new Invoice(lines, StateKind.incomplete);
var invoiceWithNewItem = invoice.AddLine(new InvoiceLine(1, 20));
Console.WriteLine($"older total -> {invoice.Total}");
Console.WiteLine($"new total -> {invoiceWithNewItem.Total}");
What's really important here is that no matter how many times you run Total for invoice variable, it'll return 20 always because a) any method that modifies the object invoice return a new invoice instance, b) because the total calculation does not keep any internal data.
Now, we're trying with customer objects
Customer customer = new Customer(500);
var invoiceCanceled = invoice.Cancel();
var customerWithInvoiceCanceledProcessed = customer.CancelInvoice(invoiceCanceled);
Console.WriteLine(customer.Balance); //500
Console.WriteLine(customerWithInvoiceCanceledProcessed.Balance); //520
You can find the code here.
Conclusions and next steps/challenges
We saw a very practical introduction to functional programming - so I hope :) - and how maths can help us to program through functions that:
- Just have a single responsibility.
- Have no side effect.
- Use referential transparency.
We saw how this impacted on the design of our application. And how it helps us to think differently when solving a problem.
But we have only scratched the surface, it leaves us with some questions that will be seen in other articles such as:
1 - There is no doubt that immutable data are safe, but what about performance? If I have to add 1000 items to the invoice it will create a thousand different instances of the list to finish up using the last one. What about memory?
2 - If the methods do not save the state that is going to be mutated, what happens to the index variables of the for command or to the item variable in a foreach command?