A Decalogue for the Extreme Functional Developer
Andrea Chiarelli
Posted on May 25, 2020
So, you’ve heard about functional programming. And you’ve heard that it a good and right thing. But you’re afraid to learn new programming languages and all that new weird stuff with strange names.
Be aware that functional programming is not a programming language. It’s a programming paradigm, a programming mindset. It is based on one fundamental principle:
A program is a mathematical function.
I hope you already know what a mathematical function is:
A binary relation over two sets that associates every element of the first set with exactly one element of the second set.
Once you’ve understood how functions work in Maths, nothing can stop you anymore to apply the functional paradigm to your daily programming.
But if you still have issues in practicing the functional mindset, then follow these commandments, and your application will be free from evil.
I — Thou shalt not have side effects
Side effects are the evil in functional programming. Actually, they are the evil in any programming paradigm. They represent the uncontrollable: you take one action, and other actions are triggered without your explicit consent.
If you define the function sum(x, y), you expect that it returns the sum of x and y. You don’t expect that it also does something else, like, for example, updating a database or incrementing a global variable. If that function does something else and you don’t know what, there’s a good chance your program is getting out of control. You may not be able to predict the result of your desired computation.
The sum of different side effects will eventually generate chaos.
II — Remember that every function is a pure function
Mathematical functions are pure. That means they have the following properties:
- For a given set of arguments, the function always returns the same result.
- The function has no side effects.
In other words, the output of a function is predictable, and you get exactly and only the result you requested.
III — Functions without parameters make no sense
As per definition, a function is a binary relation. If your function has no parameters, you are not defining any relation; you are not defining any function. So, never accept functions as the following in your codebase:
function giveMeANumber() {
return Math.floor(Math.random() * 100);
}
IV — Functions without output make no sense
Once again, if your function has no output or returns void, you are not defining a binary relation. You are not defining a function. So, never accept functions as the following in your codebase:
function doSomething() {
console.log("I've done something!");
}
V — Functions always returning the same value are actually constants
Consider the following function:
function getPi() {
return Math.PI;
}
Why should you use a function instead of using Math.PI directly?
Nothing else to add.
VI — Thou shalt not change your function’s parameters
The parameters of your function are sacred. You don’t have to touch them. If you change them, you are committing a side effect.
Consider the following function:
function normalizeFullName(person) {
person.fullName = `${person.firstname} ${person.lastname}`;
return person;
}
This function is changing the person parameter. And this is a bad thing! It is a side effect. And this function is not pure.
If you want your function to remain pure (and you should), don’t touch its parameters. They are immutable. The previous function should be rewritten as follows:
function normalizeFullName(person) {
let myPerson = Object.assign({}, person);
myPerson.fullName = `${myPerson.firstname} ${myPerson.lastname}`;
return myPerson;
}
VII — Thou shalt replace if statements with ternary operators or functions
You use the if statement very often, and you don’t see anything wrong with it. Take a look at this function:
function getTheLongestString(x, y) {
let theLongestString;
if (x.length < y.length) {
theLongestString= y;
} else {
theLongestString= x;
}
return theLongestString;
}
Look carefully at your if. It is committing a side effect! It is changing a variable that is outside its scope.
You may say: “It’s not that bad. After all, it is a local variable, a variable declared within the function”. If you’re going to get a functional mindset, you can’t let it go. If you want to become an extreme functional developer, you should use a functional approach wherever it is possible.
Use the ternary operator instead of the if statement, and rewrite your function as follows:
function getTheLongestString(x, y) {
return ( x.length < y.length ? y : x );
}
Your code will be more concise, and you don’t risk commiting side effects.
VIII — Thou shalt replace loops with Higher-Order functions
Even loops are a source of side effects. Consider the following function definition:
function getTheLongestStringInAList(stringList) {
let theLongestString = "";
for (let i=0; i < stringList.length; i++) {
if (stringList[i].length > theLongestString.length) {
theLongestString = stringList[i];
}
}
return theLongestString;
}
Your loop is changing variables, and you might lose control of this soon. You should avoid loops because of their harmfulness for your code’s purity. You should use Higher-Order functions like map(), filter(), and reduce() (or the equivalent in your favorite language).
Rewrite your function as follows:
function getTheLongestStringInAList(stringList) {
return stringList.reduce(
(theLongestString, currentString) =>
currentString.length > theLongestString.length ?
currentString
:
theLongestString
,
""
);
}
You can do even better by composing your functions:
function getTheLongestStringInAList(stringList) {
return stringList.reduce(getTheLongestString, "");
}
Everything will be easier.
If your language doesn’t support those Higher-Order functions or you don’t understand them, you can use recursion instead of loops:
function getTheLongestStringInAList(stringList) {
let firstString = stringList[0];
let remainingList = stringList.slice(1);
return remainingList.length === 0 ?
firstString
:
getTheLongestString(firstString, getTheLongestStringInAList(remainingList));
}
Your functional mindset will gain a lot from it.
IX — Your variables must be constants
And since we said that changing parameters and variables is a risk for your functional mindset, why are you still using variables?
If you want to become a fully functional developer, you shouldn’t rely on variables. Variables don’t have to change. They should be immutable. They are just placeholders for values. They are constants.
So, rewrite your functions as in the following example:
function getTheLongestStringInAList(stringList) {
const firstString = stringList[0];
const remainingList = stringList.slice(1);
return remainingList.length === 0 ?
firstString
:
getTheLongestString(firstString, getTheLongestStringInAList(remainingList));
}
Make sure that your programming language has real constants and not just constant references as in JavaScript.
X — Thou shalt not use variables at all
If you want to reach an extreme functional mindset, you should totally give up variables. They are useless. They are obstacles along your way to the functional perfection: a program is a mathematical expression!
Your function should look like the following:
function getTheLongestStringInAList(stringList) {
return stringList.slice(1).length === 0 ?
stringList[0]
:
getTheLongestString(
stringList[0],
getTheLongestStringInAList(stringList.slice(1))
);
}
Or even better, like so:
const getTheLongestStringInAList = (stringList) =>
stringList.slice(1).length === 0 ?
stringList[0]
:
getTheLongestString(stringList[0], getTheLongestStringInAList(stringList.slice(1)));
👏 Congratulations! Welcome to Lisp.👏
Now, follow this Decalogue, and the functional heaven will be yours. You have no excuse.
This post was originally published on my Medium profile
Posted on May 25, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.