Javascript (and its ecosystem) is a language that’s evolving really fast. New libraries and frameworks appear at a frantic pace, offering new architectures and functionalities. In past articles we’ve seen what TypeScript has in store for us as well as how to easily migrate from an old project to this JS Superset. Today’s article will be slightly different: this time I’d like to show a couple of examples about how we can apply Functional Programming in JavaScript.
To that end, we’ll use Ramda-fantasy, a library of powerful abstractions offering a big set of Monads, and another library called Ramda, which will help us build functional pipelines and achieve immutability. Before we get started, though, a reminder: these aren’t the only libraries that offer these functionalities; search long enough and you can easily find perfectly valid alternatives.
Functional programming in Javascript
Even though functional programming can vastly improve an application’s code , its principles can prove a bit challenging at first. Since explaining all of them in detail would take a lot of time, we’ve decided to use two practical examples of code to introduce these concepts, and if they manage to pique your interest then we strongly recommend that you keep investigating the world of functional programming in Javascript.
1- Maybe Monad
In the first example we’ll find a way to avoid verifying if a variable is Null. Let’s imagine that in our application we can find users with this format:
const someUser = {
name: 'some_name',
email: 'some@email.com',
settings: {
language: 'sp'
}
};
And we have a function that returns a welcome message in the language that the user has set.
const allGreetings = {
'en': 'Hello!',
'sp': 'Hola!',
'fr': 'Bonjour!'
};
const getGreetingForUser = (user) => {
//to be implemented
}
Let’s see an implementation of the ‘getGreetingForUser’ function following an imperative model:
const getGreetingForUser = (user) => {
if (!user) {
return allGreetings.en;
}
if (user.settings && user.settings.language) {
if (allGreetings[user.settings.language]) {
return allGreetings[user.settings.language]
} else {
return allGreetings.en;
}
} else {
return allGreetings.en;
}
};
console.log(getGreetingForUser(someUser));
As you can see, we had to check if the user already existed, if he/she had a language set and if said language is among the ones that have a welcome message prepared. And if something goes wrong, we return a message in the default language, english in our case.
Now, let’s see the same function but this time we’ll use Functional Programming in its implementation:
const getGreetingForUser = (user) => {
return RamdaFantasy.Maybe(user)
.map(Ramda.path(['settings', 'language']))
.chain(maybeGreeting);
};
const maybeGreeting = Ramda.curry((greetingsList, userLanguage) => {
return RamdaFantasy.Maybe(greetingsList[userLanguage]);
})(allGreetings);
console.log(getGreetingForUser(someUser).getOrElse(allGreetings.en));
This solution might seem confusing at first, but once you understand the concepts we’re using it isn’t so.
To deal with a potential null or undefined case we’ll use the Maybe Monad. This allows us to create a wrapper around the object and assign a default behaviour for null objects.
Let’s compare both solutions:
//Instead of verifying if the user is null
if (!user) {
return allGreetings.en;
}
//We’ll use:
RamdaFantasy.Maybe(user) //we add user to the wrapper
———————-
//Instead of:
if (user.settings && user.settings.language) {
if (allGreetings[user.settings.language]) {
//We’ll use:
<userMaybe>.map(Ramda.path(['settings', 'language'])) //If data exists, map will use it
——————–
//Instead of returning the default value in the else:
return indexURLs['en'];
//We’ll use:
.getOrElse(allGreetings.en) //getOrElse will either return someuser’s valuer or the default value that we specified.
2- Either Monad
The Maybe Monad is very useful when we know the default behaviour when there’s a null error.
But if we have a function that throws an error, or we chain various functions that throw errors and we want to know which one is failing, we can instead use the Either Monad.
Now let’s imagine we want to calculate a product’s price, taking into account VAT and possible discounts. We already have this code:
const withTaxes = (tax, price) => {
if (!_.isNumber(price)) {
return new Error("Price is not numeric");
}
return price + (tax * price);
};
const withDiscount = (dis, price) => {
if (!_.isNumber(price)) {
return new Error("Price is not numeric");
}
if (price < 5)
return new Error("Discounts not available for low-priced items");
}
return price - (price * dis);
};
const isError = (e) => e && e.name === 'Error';
const calculatePrice(price, tax, discount) => {
//to be implemented
}
Let’s see an implementation of the ‘calculatePrice’ function following an imperative model:
const calculatePrice = (price, tax, discount) => {
const priceWithTaxes = withTaxes(tax, price);
if (isError(priceWithTaxes)) {
return console.log('Error: ' + priceWithTaxes.message);
}
const priceWithTaxesAndDiscount = withDiscount(discount, priceWithTaxes);
if (isError(priceWithTaxesAndDiscount)) {
return console.log('Error: ' + priceWithTaxesAndDiscount.message);
}
console.log('Total Price: ' + priceWithTaxesAndDiscount);
}
//we calculate the final price of a product that’s worth 25, with 21% VAT and a 10% discount.
calculatePrice(25, 0.21, 0.10)
Now let’s find out how we can rewrite this function using the Either Monad.
Either has two constructors, Left and Right. What we want to achieve is to store the exceptions to the Left constructor and the normal results (happy path) to the Right constructor.
First of all, we’ll change the already existing withTaxes and withDiscount functions so they return Left if there’s an error and Right if everything’s fine:
const withTaxes = Ramda.curry((tax, price) => {
if (!_.isNumber(price)) {
return RamdaFantasy.Either.Left(new Error("Price is not numeric")); //we assign the error to Left
}
return RamdaFantasy.Either.Right(price + (tax * price)); //we assign the result to Right
});
const withDiscount = Ramda.curry((dis, price) => {
if (!_.isNumber(price)) {
return RamdaFantasy.Either.Left(new Error("Price is not numeric")); //we assign the error to Left
}
if (price < 5) {
return RamdaFantasy.Either.Left(new Error("Discounts not available for low-priced items")); //we assign the error to Left
}
return RamdaFantasy.Either.Right(price - (price * dis)); //we assign the result to Right
});
Then, we create a function for the Right case (show the price) and another for the Left case (show the error) and we use them to create the Either Monad:
const showPrice = (total) => { console.log('Price: ' + total) };
const showError = (error) => { console.log('Error: ' + error.message); };
const eitherErrorOrPrice = RamdaFantasy.Either.either(showError, showPrice);
Lastly, we just need to execute the Monad to calculate the final price:
//we calculate the final price of a product that’s worth 25, with 21% VAT and a 10% discount.
eitherErrorOrPrice(
RamdaFantasy.Either.Right(25)
.chain(withTaxes(0.21))
.chain(withDiscount(0.1))
)
Conclusion: functional programming in Javascript
As we can see, once we’ve broken down the code with both the Maybe and Either monads, it doesn’t feel that complex. And if used correctly, they can make our code easier to read and maintain.
The only inconvenient would be the initial barrier we need to overcome, but that can be done watching some examples and doing some tests.
We hope that with this article you can start to explore the Functional Programming in JavaScript and/or in any language you work with.
The post Functional Programming in JavaScript appeared first on Apiumhub.