š® Functional Programming for Humans ā Foundations
Sameer Kumar
Posted on July 29, 2024
Functional programming (FP) is like the cool, rebellious middle child of the programming (language) family. Instead of following the traditional imperative way of doing things step-by-step, FP focuses on creating clean, predictable code by treating functions as first-class citizens. Itās like the stand-up comedy of coding: sharp, witty, and always on point.
A lot of jargon in the introduction itself? Thought so.
Some quick bullet points to wash off that cult image of FP
- There isnāt a clear line dividing functional programming languages from other languages.
- You can write functional code in almost all languages. Even in boundaries of strict OOP languages.
- Your code doesnāt need to be 100% functional. Try to keep it as immutable and pure as humanely possible.
- FP doesnāt make you cool. It may do a little bit of idiot-proofing though.
Today weāll go through a few jargon to set the stage for upcoming adventures. Iāll stick to dumb JavaScript for code examples. Letās keep Haskell/Elixir show off for later, hold peace itāll come.
- Pure functions
- Immutability
- Side effects
- Higher order functions
Pure functions
The simplest and most important concept of functional programming is to make code outputs consistent. The same input(s) should give the same output. As simple as that. No T&C applied.
You send 2
and 3
to a function called sum
. It should always give you 5
and never 23. Javascript joke!
So you ask: Dear Sameer, itās dead simple to ask for any computer program let aside this FP. Doesnāt all code work like that, or at least should work like that?
Well easier said than done in large codebases where everything is like a mess of randomness. Letās take a few simple examples.
Pure functions:
function add(a, b) {
return a + b;
}
// Example usage:
console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5
function toUpperCase(str) {
return str.toUpperCase();
}
// Example usage:
console.log(toUpperCase("hello")); // "HELLO"
console.log(toUpperCase("hello")); // "HELLO"
Impure functions:
let counter = 0;
function incrementCounter() {
counter += 1;
return counter;
}
// Example usage:
console.log(incrementCounter()); // 1
console.log(incrementCounter()); // 2
// Outputs a random number between 1 and 10
function getRandomNumber() {
return Math.floor(Math.random() * 10) + 1;
}
// Example usage:
console.log(getRandomNumber()); // 2
console.log(getRandomNumber()); // 6
However rudimentary, that gives a general idea. A pure function will always be consistent. Impure ones can and will backstab you in most unexpected places. Imagine, one time your bank account shows $150,000 and you refresh the page to see $150. Good enough for a minor heart attack, aye?
Immutability
No change == No mutation == Immutability == Trust
Now that we have semi-formally defined it, letās look at how it works. After all, programming is all about changing one data into another. Weāll write a small mutable code and see the perks of its immutable version.
function updateAge(obj, newAge) {
obj.age = newAge;
return obj;
}
// Example usage:
const person = {
name: 'Alice',
age: 25
};
console.log(updateAge(person, 26)); // { name: 'Alice', age: 26 }
console.log(person); // { name: 'Alice', age: 26 }
Letās dissect. Good Alice is 25 years old, to begin with. If I come to the last line directly and want to know the age of Alice(aka person
), Iāll have to track this person
object everywhere and carefully understand who modified it and āhowā.
Letās fix this mutation:
function updateAge(obj, newAge) {
return { ...obj, age: newAge };
}
// Example usage:
const person = {
name: 'Alice',
age: 25
};
console.log(updateAge(person, 26)); // { name: 'Alice', age: 26 }
console.log(person); // { name: 'Alice', age: 25 }
Now, anywhere we meet this person
, we are guaranteed to know its state by seeing itās original definition. If this new value is required for something, go ahead and keep it in a new updatedPerson
variable. If someone later wanna know the age of updatedPerson
, heāll just jump to the place where it is first defined. Savvy?
Benefits you ask. Here are a few:
- It becomes easy for the languageās internal tools to clean the used memory, aka, garbage collection.
- An implicit guarantee that value will remain consistent throughout the life of a variable.
- A ton of predictability in the code. You can just skim a few lines and tell what the value is at any point in time, without reading the entire code.
- Idiot proofing. You know that someone else wonāt modify what you read the first time. Ah! Do those edited WhatsApp messages ring a bell?
Higher order functions
This one aināt much. Simply said, a function can take and return functions too, just like it plays with regular data.
Depending on your previous programming experience this may look normal or whaaaat. I expect no in-between experience.
When you treat functions as first-class citizens, you can do some pretty amazing things by combining or passing them around. Letās see a few examples:
// High-order function that returns another function
function makeMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
// Example usage (custom tailored functions on the fly)
const double = makeMultiplier(2);
const triple = makeMultiplier(3);console.log(double(5)); // 10
console.log(triple(5)); // 15
import {map} from 'lodash';
const numbers = [1, 2, 3, 4, 5]; // This map function takes a list of some data and a function.
// Basically running the same function on each item in the list.
const doubledNumbers = map(numbers, number => number * 2 );// Example usage
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
Side Effects
Here the real trouble starts. As the name suggests, your code will poke its nose into things that are beyond its scope. If the function modifies or triggers something thatās outside its scope, it can wake the dead from graves. We may never know the full extent of what this external call did until you are neck deep in that call stack rabbit hole.
Too many fancy words? My sympathies. Letās see the code you wrote some days back.
Yeah Yeah Yeah! Now you tell me that you didnāt know that bunnies too have nukes. Classic!
Letās create a cocktail of what we learned from pure functions and immutability. Side effects totally rack jacks our two beloved concepts.
const numbers = [1, 2, 3];
export function addNumber(number) {
numbers.push(number); // Modifies the original array
}
export function viewArray() {
console.log(numbers); // Prints numbers array
}
// Sinjo calling same method in different files
viewArray() // 1,2,3
viewArray() // 1,2,3
viewArray() // 1,2,3
// nastyuser_123 called it somewhere randomly
addNumber(4)
viewArray() // 1,2,3,4
These functions are making side effects on the numbers array. Our good user Sinjo is compulsively printing arrays using viewArray
function and getting the same results everywhere, pseudo-smiling in purity. Then someone called this addNumber(4)
and now his system is broken and he doesnāt even know why.
Sinjo wants answers, Sinjo wants justice! ā¤ļøāš©¹
Thatās all folks for today. Reflect on this jargon list. Whatever we went through should give a clear idea about what problems we are trying to solve. In the next piece, weāll get our hands dirty with some deeper concepts. Ciao.
Want to connect?
Posted on July 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 20, 2024