Introduction
I’ve come across the word functional programming (FP) from time to time but never really cared about it, thinking it's some conservative old-school paradigm opposed to Object Oriented Programming, similar to Procedural Programming.
Luckily I came across the expression FP so many times recently I started to look it up. After reading a book on the topic (link below) i realized my first assumption was wrong, completely wrong actually.
Benefits of FP
Functional programming is very simply put a coding style where you chop up your logic into smaller, much smaller functions. These functions can be reused and tested individually. As they are tiny, they are easy to grasp and can (should!) be named informatively and non-generic.
All this will help us keeping code in line with the most important yet basic design principles (DRY, SOLID, POLE, POLK etc).
Example
Consider a list of clients objects (simplified of course to focus on the difference).
const clients = [
{ id: 1, hasDevice: true, age: 72 },
{ id: 2, hasDevice: false, age: 63 },
{ id: 3, hasDevice: false, age: 84 },
{ id: 4, hasDevice: true, age: 75}
]
Now, let’s say we want to get all id of clients without device and older than 70.
let elderlyWithDevice = []
const MAX_AGE = 70
// 1. the classic for-loop
for (let i = 0; i < clients.length; i++) {
if (clients[i]).hasDevice && clients[i].age > MAX_AGE) {
elderlyWithDevice.push(clients[i].id)
}
}
// -> [3]
// 2. enhanced for-loop
for (const client of clients) {
if (client.hasDevice && client.age > MAX_AGE) {
elderlyWithDevice.push(client.id)
}
}
// -> [3]
FP(L) approach:
const olderThan70WithDevice = client => client.age > 70 && client.hasDevice
const getId = client => client.id
let elderlyWithDevice = clients.filter(olderThan70WithDevice).map(getId)
// -> [3]
Benefits of FP revisited
Hopefully you can see the power of having declarative code. When reading the code to understand it (we tend to do that a lot!) we can accept filter(olderThan70WithDevice)
and move on, we don't have to dive into this logic to understand what is happening, unless we choose to. Having imperative code on the other hand, we need to understand the logic to move on. Another benefit is by separating the logic of the filter function (predicate) and the map function (transform) to separate functions they can now be reused and tested individually.
Stay DRY with closures
By using closures we can even DRY up our age filter. Lets create a function that takes a number (age) and returns a filter we can use the client. An age filter factory if you like.
We declare a function that takes a number and return a new function that takes a client which returns a boolean.
number -> (client -> boolean)
function filterOlderThan(age) {
return function (client) {
return (client.age > age)
}
}
This can now be used to create new filters.
const isOlderThan70 = filterOlderThan(70)
const isOlderThan80 = filterOlderThan(80)
// use as filter
clients.filter(isOlderThan70).map(getId)
// [1, 3, 4]
clients.filter(isOlderThan80).map(getId)
// [3]
// use in if
if (isOlderThan70(clients[0])) {
...
}
For the purpose of this FP introduction I have used simple, trivial even, examples but as code gets more complicated the more relevant the strengths of FP get.
Disclosure: I'm writing this as a humble newbie on the subject, I am not an expert functional programmer. This is my first post and English is my second language.
Read more
The spark for me was this book, https://github.com/getify/Functional-Light-JS
Top comments (2)
:-/ ...
You made a test with
client.hasDevice &&
and the variable iselderlyWithoutDevice
And the FP version don't test
client.hasDevice
, so we can't make a comparaison ... :-/Oh, it's actually a mess, yes you are right, can't believe this slipped in. I had a real life version containing much more logic and company specific bits I refactored out for the example. I'll update, my bad.