I've been a Software Engineer for 5+ years, and when I started learning how to tell computers to do what I wanted them to do, I started with JavaScript and then I found out about Functional Programming and I've tried to keep using this paradigm as much as I can.
But what is Functional Programming?
From Eric Elliott(I'm a huge fan of his writing):
Functional programming (often abbreviated FP) is the process of building software by composing pure functions, avoiding shared state, mutable data, and side-effects.
FP is a programming paradigm, which means that it's the way we think when constructing software based on some principles or rules. It takes some time to get used to FP if you have been building software in a non functional programming approach, but it's worth it.
FP is all about immutability, pure functions and prevents side effects in general, and add to that the declarative way of writing code.
So, what ideas are we going to through here?
- Pure Functions and Side Effects
- Immutability
- Declarative vs Imperative
- Higher-order functions
- And any other comment
Pure Functions
A Pure Function is a function that, given the same input, always returns the same output, and do not have side effects.
const add = (a, b) => a + b;
console.log(add(1, 2) === 3); // true
In the previous example, the add
function will always return the same result for the same parameters: 1 + 2 = 3
. You can expect/predict what it's going to return given specific parameters and it doesn't change any state outside of its scope.
A function is not considered pure
when it either depends on some value/state that can change over time or when it trigger side effects. For example:
// This function depends on a random number, given the same inputs
// it won't always return the same output, hence NOT PURE.
function getRandomNumberFromRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Another case in which a function could not be considered pure, is when it depends on dates to operate, and these dates cannot be controlled from the outside scope of the function.
// This function depends on the current date
// calling this function several times
// will return a different result, hence NOT PURE.
const getCurrentTimestamp = () => Date.now();
// Now, if we look at this version
// it will return the same output for the same input
// hence PURE
const getTimestamp = date => date.getTime();
But then, what is a side effect?
A side a effect is any app state change that can be noticed and/or observed from outside of the called function and not its returned value. What are we talking about here?
- Database changes
- Logs to the console or to a file
- Making requests through the network to change information
- Starting up some external processes
- Making calls to functions that actually have side effects
Does this mean that all our code must NOT have side effects? No, it just means that we can keep separate our side effect functionality from our pure functionality, like having to different domains to reason about, cause they really complement each other.
We can have a bunch of functionality that operates over information as pure functions and using the result values to write to the database, to trigger a queue or send emails.
There's no problem with that, the mental model that we need to keep is that, when we are adding side effects to our system, we will need to write tests that cover those side effects, maybe mocking those side effect services or using a test database for example. Cause the real situation here is that side effects are not deterministics, so we need to manipulate them or the variables surround them in order to get some expected value.
Immutability
This refers to the concept of having values or data structures that cannot be modified after being created, if you want to modify them you need to create a copy of said values or data structures with, or without, the information your system is concerned about at the time.
In JavaScript, we constantly refer to this immutability behaviour on values stored as constants by using the const
keyword, but this doesn't prevent us from modifying objects. The keyword const
only means that the variable cannot be re-assigned, which is a different concept.
A way in which we can achieve immutability by freezing an object, using Object.freeze()
.
Let's see an example:
const wallet = {
balance: 100,
currencySymbol: 'WAX',
};
const walletReference = wallet;
wallet.balance = 15;
console.log(wallet.balance); // 15
console.log(walletReference.balance); // 15
The wallet object has been mutated by modifying the balance
property, and since the wallet object is a shared state between 2 variables(by reference), the balance change will be reflected in both variables, and that have been troublesome in many systems through the years, cause sometimes having shared states can lead to unexpected behaviours and breaking changes we as engineers are not aware of.
So, which could be one way to just not modify the state? Let's take the same wallet object we had before:
const addBalanceToWallet = (balance, wallet) => ({
...wallet,
balance: wallet.balance + balance
});
const wallet = Object.freeze({
balance: 100,
currencySymbol: 'WAX',
});
wallet.balance = 1;
console.log(wallet.balance); // 100 -> the value stays unchanged
const walletReference = wallet;
const updatedWallet = addBalanceToWallet(12, wallet);
console.log(wallet.balance); // 100
console.log(walletReference.balance); // 100
console.log(updatedWallet.balance); // 112
Now in this case for us, we got a whole new wallet with the updated value while having the original object, and its references, unchanged/non-mutated. But Object.freeze
doesn't really achieve immutability as we'd like to, cause it only "freezes" the object at the top/first level, meaning that any other objects attached to it can still be modified. We can prevent this from happening by also freezing those objects or just using some immutability library, such as Immutable.js.
In my personal case, I've encountered multiple issues in the past by having shared state and handling mutable objects non-properly. I always try to favor immutability, using copies and avoiding shared states as much as possible, when applicable.
Declarative vs Imperative
What are those things?
Declarative and Imperative programming models, are styles for building blocks of code in an application, based on different mental models. In FP, we always favor the declarative model over the imperative one.
Imperative mental model
The imperative approach focus itself in describing how to do things, meaning it is focused in the flow control of the program. An example:
const users = [
{
name: 'John',
lastname: 'Due',
},
{
name: 'Some',
lastname: 'Dude',
},
];
const allLastNames = [];
for (const user of users) {
allLastNames.push(user.lastname);
}
console.log(allLastNames); // ['Due', 'Dude']
Declarative mental model
The declarative approach focus itself in describing what to do, meaning it is focused in the data flow of the program. The imperative's how gets abstracted into some other function or layer. An example:
// users/utils.js
export const getLastName = user => user.lastname;
// main.js
import { getLastName } from './users/utils';
const users = [/* Imagine the users as before */];
const allLastNames = users.map(getLastName);
// The following will print ['Due', 'Dude']
// Still the same result, but in a declarative way.
console.log(allLastNames);
Now we don't really care about the details regarding how to get the last names from the users, we just focus in what what we do to get them.
I personally like the declarative approach more, to me it's more readable and it communicates more of the business domain logic than the technical details used to do things in general. Some other people like the imperative approach better cause they feel like it reads better and the intent is clearly stated, but you can say the same about the declarative approach too.
Higher-order functions
This one is a concept that I like a lot and it's used in many codebases when doing FP.
In many FP languages, functions are first class citizens
. This means that a function is treated like a value: it can be passed around to other functions via parameters, it can be returned from within a function and it can be assigned to a variable.
Higher-order functions are those that accept functions as parameters and/or use a functions as their return values.
Let's see how this plays out:
// We are returning a new function when calling add()
// this is called `partial application`
const add = a => b => a + b;
// `add10` is now a function that can be called
// it will always add 10 to the value passed
const add10 = add(10);
const numbers = [1, 2, 3, 4, 5, 6];
// Here we pass our add10 function as parameter/argument
// to the Array.prototype.map method
console.log(numbers.map(add10));
// Here we pass a whole new function to the
// Array.prototype.reduce method in order to convert
// the whole array into a different data type
// in this case, a number
const totalSum = numbers.reduce((total, num) => total + num, 0);
console.log(totalSum); // 21
In the previous example we saw how we assigned a function to a variable, giving that function the variable's name. We played passing around functions to other functions and returning them too, and that really displays how powerful this concept is and how many things it enables:
- Partial Application.
- Currying.
- Function Composition.
- Callbacks for async operations, events and so on.
So it is REALLY powerful, and if you use JavaScript daily, you use this concept a lot, just by mapping an array. I use it everyday and I really like it, it has helped me a lot when building software and when I need to compose objects or pipe data together to trigger some N consecutive operations over data and it simplifies my code a lot.
Conclussion
This was just me going through some of the concepts regarding Functional Programming, but I didn't cover everything, just the things I believe might motivate someone to go and try this paradigm.
I cannot stress this enough: this paradigm DOES NOT replaces Object Oriented Programming(with or without classes), in fact, they complement each other, Reactive Programming also applies here. You can build really robust software if you're able to bundle this paradigms together and it can be a great coding, and learning, experience.
I've used it for some years now, and opening my mind to FP has helped me see and reason about code, and problems, in a different way. So I encourage you to try it, even if it's just as a learning experience. You might not regret it.
--
If you want to learn more about this, I'll recommend you to go through the Master the JavaScript Interview series and the Composing Software book, both by Eric Elliott.
Top comments (0)