In this post, we’ll explore common concepts in a functional programming language in TypeScript.
Functional programming as a concept has been around for years. But in the last 5-6 years, somehow, it has become the popular kid on the block.
Most of the folks I’ve talked with in recent years have been intimidated by functional programming, and specifically the popular languages, like, Scala, Erlang, Haskel, etc.
Well, my (official) introduction to functional programming was with Scala, and since I was used to verbose OO languages like C#, Java or JavaScript, Scala’s programming style required some time to get used to.
But, one thing that didn’t take getting used to, was functional programming.
What I didn’t realize was, that, since I had been coding in JavaScript/TypeScript for a few years now, I already knew a lot about typical functional programming concepts (like functions being first-class citizens, high-order functions, closures, etc).
Considering the statistic on stackoverflow, if you are developer, then there’s a 67.8% probability you already know functional programming. JavaScript has been the most popular programming language for the seventh year in a row now.
When it comes to top functional programming languages, JavaScript/TypeScript would, at best, get an honorable mention. But believe me, with the introduction of ES6 and evolution of TypeScript, it already supports a lot of functional programming concepts.
And if you’ve been coding in JavaScript/TypeScript for a few years now, you are (almost) a functional programming wizard.
So, what are some of the common concepts in a functional programming language:
- Functions as first-class citizens and higher-order functions
- Pure Functions
- Closures
- Currying
- (Tail) Recursion
- Immutability
We’ll try to explore at all these concepts in TypeScript in the following sections.
Functions as First-Class Citizens and Higher-Order Functions
First-class citizens: When we say function
s are first-class citizens, it means, you can assign function
to variables, pass them as arguments and have them as return types.
let funcAdd = (a: number, b: number): number => {
return a + b;
}
console.log(funcAdd(5, 4));
High-order functions: A function
is called high-order, when it can accept one or more functions as parameters or if it’s return type is function
.
type paramFunc = (k: string) => number;
function higherFunc(fn: paramFunc, arr: string[]): number[] {
let returnArray: number[] = []
arr.forEach(elem => {
returnArray.push(fn(elem));
});
return returnArray;
}
let lengthFunc = function (param: string): number {
return param.length;
}
console.log(higherFunc(lengthFunc, ["hello", "bye"]));
Pure Functions
- Pure Functions are methods which do not alter any external state, for example, any class level variable, external DB, file, etc.
- Also, any external factors (like, time of the day) shouldn’t influence their behavior.
- A pure function should always return the same output for the same set of inputs.
var impureFunc = (): number => {
return new Date().getMonth();
}
console.log(impureFunc());
var funcAdd = (a: number, b: number): number => {
return a + b;
}
console.log(funcAdd(5, 4));
Closures
Closures are one of the cooler concepts in programming. Although they could lead to difficult to debug issues, but once you get the hang of it, you’ll love them.
Closures basically store the state of outside data in the function. If, at the time the function is compiled, it has a reference to a global (or parent function level) variable, it would continue to hold its value even when the parent function containing the variable goes out of scope (or has closed).
var timerFunc = (name: string) => {
let functionVar = `Hello ${name}`;
setTimeout(() => {
console.log(`after 1000 ms - ${functionVar}`);
}, 1000);
console.log("exiting timerFunc");
}
timerFunc("chinmoy");
As you can see from the above example, although the timeFunc
exited, the functionVar
parameter’s value was still retained inside the function closure (cool right). Hence, when the timeout
elapsed, they could still print the value of functionVar
.
Currying
Currying is a very popular functional programming concept. Every language has a different flavor for currying (pun not intended), but TypeScript probably has the most bland flavor (pun intended ;)) of all.
It is basically a technique to construct functions that allows for partial application of the function’s arguments. What it basically means is, you can either call a curried function with all its parameters, or you could call the function with limited parameters and it would return a function which accepts the rest of the parameters.
function curryAdd(a: number): (b: number) => number {
return (b: number): number => a + b;
}
let moreAdd10 = curryAdd(10);
console.log(moreAdd10(3));
console.log(curryAdd(3)(10));
(Tail) Recursion
It’s a very popular opinion that functional programming favors recursion v/s looping. Well, although recursion is fancy and results is concise code, it can incur performance losses, since the results of every recursion are stored on a stack.
Hence, most functional programming languages favor something known as Tail Recursion.
Let’s looks at the most trivial sample code for recursion. Getting factorial of a number:
let factFunc = (param: number): number =>
param== 0 ? 1 : param* factFunc (param- 1);
console.log(factFunc (10));
const factFuncTail = (param: number): number => fact(1, param);
const fact = (accum: number, val: number): number =>
val == 1 ? accum : fact(accum * val, val - 1);
console.log(factFuncTail(10));
I found the tail recursion function a little difficult to understand but for engines optimized for tail recursion, it performs on par with looping.
Unfortunately, not all JavaScript engines are optimized for tail recursion, so you might incur performance penalties when writing recursive code in JavaScript/TypeScript.
Immutability
It’s one of the core best pratices when writing functional code. One should always strive to use immutable data types. Having these results in more robust and reliable code which is easier to debug.
Unfortunately, TypeScript doesn’t provide strictly immutable data structure. We have a notion of Immutability when using “const
”, but these are not strictly immutable.
Let me show you what I’m talking about:
const cStr = "Hello, I am Immutable";
cstr = "Trying to mutate here";
const lst = ["A", "B", "C", "D"];
list = ["X", "Y"];
list.push("X");
There are a few other characteristics of functional programming languages, but the core idea which I’m trying to convey is that if you have experience coding in JavaScript/TypeScript, you already know functional programming.
Of course, this doesn’t mean there wouldn’t be a learning curve when learning a language like Scala, but, I believe it’ll not be very steep.
Happy coding!!