Writing functional programming can induce a Zen like experience. With clean, short blocks of code that reveal their intent and inner workings plainly to you functional C# untangles the knots, and declutters the space.
No more side effects
Take a deep breath in. Hold it for 5 seconds. Exhale.
It's time to think functionally, and it's time to say goodbye to those nasty, no-good side effects.
- Side effects occur when you modify state outside of your current scope, mutating state unknowingly somewhere else in the program. This leads to unwanted behaviour, and unwanted cause-and-effects.
- Instead prefer functions that don't have side effects. These functions are known as pure functions. (disclaimer: writing pure functions is quite addictive - once you start you just can't stop)
The Zen Code Advises:
AVOID: Impure functions
public void UpdatePrice(decimal vat, decimal cost, decimal markup)
{
this.price = (cost + markup) * (1 + vat);
}
PREFER: Pure functions
public decimal PriceIncludingVat(decimal vat, decimal cost, decimal markup)
{
return (cost + markup) * (1 + vat);
}
price = PriceIncludingVat(0.2m, 10m, 2m);
Calling PriceIncludingVat
doesn't modify existing state; it returns new state. You can call it as many times as you want and your current price
won't be affected until you actually set it.
Practice creating pure functions out of longer impure methods. Break the side effects out of methods and turn them into their own functions returning new state. You may find yourself untangling some really, nasty messes - it's cathartic and brings you closer to Zen.
Immutability
Close your eyes. Breathe in. Breath out. Embrace the tranquillity of immutability.
No longer will your state be mutable. In with the new
, and out with the old. By making your objects immutable, you ensure that nothing, within any scope whatsoever, can change them. This prevents side effects altogether. The only way to achieve a changed property on an immutable object is to replace that object with a new one that contains the change.
The Zen Code Advises:
AVOID: Mutable Objects
public class ZenGarden
{
public int Tranquillity { get; set; } // mutable
public int Peace { get; set; } // mutable
public ZenGarden(int tranquillity, int peace)
{
Tranquillity = tranquillity;
Peace = peace;
}
}
This leads to
var garden = new ZenGarden(10, 20);
garden.Peace = -1000;
// or even worse
public int TranquilityAndPeace(ZenGarden garden)
{
garden.Peace -= 1; // a side effect
return garden.Tranquillity + garden.Peace;
}
PREFER:
public class ZenGarden
{
public readonly int Tranquillity;
public readonly int Peace;
public ZenGarden(int tranquillity, int peace)
{
Tranquillity = tranquillity;
Peace = peace;
}
}
This leads to
var garden = new ZenGarden(10, 20);
garden = new ZenGarden(10, 21); // peace goes up, because of immutability :]
// pure function
public int TranquilityAndPeace(ZenGarden garden)
{
var calculatedGarden = new ZenGarden(garden.Tranquillity, garden.Peace - 1);
return calculatedGarden .Tranquillity + calculatedGarden .Peace;
}
- Note,
readonly
is used instead of a private setter to ensure that the values can't be changed even within theclass
itself.
No more worries about unwanted changes (side effects) - immutability ensures that nothing is mutating the same state, wave a friendly goodbye to race conditions and thread locks too.
Referential Transparency
Embrace the Zen of clarity. A referentially transparent function can be replaced by the value that it returns when it's called. It returns the same value for the same arguments regardless of its context. Referentially opaque functions do the opposite - usually due to referencing some external value which may change.
Referential transparency helps the code to read cleaner. It untangles functions that grab values from outside of their scope, and restricts them to act only on their parameters when calculating their return values.
It isolates functions, protecting them from external changes in code.
The Zen Code Advises:
AVOID: Opaque functions
public double PriceOfFood(double price)
{
// if the number of people changes, the price of food changes
// regardless of price changing
return this.numberOfPeople * price;
}
PREFER: Transparent functions
public double PriceOfFood(double price, int numberOfPeople)
{
return numberOfPeople * price;
}
Higher-Order Functions
It's first-class (functions) baby all the way. Let's pick those functions up and start treating them like the (first-class) citizens they are. C# treats functions like first-class citizens. This means that functions can be used as arguments to methods; functions can be assigned to variables; and function can even be returned from other functions.
Using functions as first-class citizens allows us to break our code into small building blocks, each of which encapsulates a little bit of (hopefully clean, pure, and referentially transparent) functionality. These small blocks can be used in many combinations to get different functionality. They can be swapped out and interchanged as our requirements change with little impact on the code base.
Generate your functionality as you need it - don't write reams of code. Let the functions behave like the moving parts we know they are.
Higher-order functions take in functions as parameters, or return functions. Because C# treats functions as first-class citizens it allows as to create higher-order functions.
The Zen Code Advises:
AVOID: Unclear lambdas
var range = Enumerable.Range(-20, 20);
range.Where(i => i % 2 == 0);
range.Where(i => i % 5 != 0);
PREFER: Named functions
Func<int, bool> isMod(int n) => i => i % n == 0;
range.Where(isMod(true, 2));
range.Where(!isMod(false, 5));
AVOID: Procedural functional calls (where applicable)
public class Car {...}
public void ChangeOil(Car car) {...}
public void ChangeTyres(Car car) {...}
public void Refuel(Car car) {...}
public void PitStop(Car car)
{
ChangeOil(car);
ChangeTyres(car);
Refuel(car);
}
PitStop(car);
PREFER: Dynamic function calls (where applicable)
public class Car {...}
public void ChangeOil(Car car) {...}
public void ChangeTyres(Car car) {...}
public void Refuel(Car car) {...}
public void PitStop(Car car, IEnumerable<Action<Car>> pitstopRoutines)
{
foreach(var routine in pitstopRoutines)
routine(car);
}
PitStop(car, new List<Action<Car>>{ChangeOil, ChangeTypes, Refuel});
The method PitStop
is much more extensible. It can easily be extended and should hardly ever need modifying. This is achieved by simply passing in the functions you want to execute as parameters, treating them like first-class citizens.
Top comments (9)
Hi Henrick, really good article, I'm going to start putting this into practice!
Also noticed the sample code 'public void PriceOfFood...' should return double.
Cheers
Hi Ferdeen, I really appreciate the positive feedback, thank you so much!
And well spotted - fixed it :]
No problem!
Was checking where I commented on the typo and could see a couple more void returns that should also return double. They are towards the top of the post.
Cheers!
Oh jeez - I checked the writing for typos but not the code - thanks again :]
No worries. It doesn't take anything away from such a good post, and I'll look forward to reading more of your work. Cheers.
For a janitor, you really know a lot. You're like the Will Hunting of programming
Hahaha, thanks :)
Love this. I get jaded about the language I code in professionally; sees C# is steeped in methodolody that is counterintuitive to what is being achieved.
Thanks Jimmy - yes exactly. The .Net team have added so much functionality to C# that you don't always know how to use it sometimes. Sticking to a clean functional approach helps; and it's a lot of fun switching your thinking from procedural to functional.