Understanding the Core Concepts
Functional programming (FP) is based on some key concepts that may seem unfamiliar at first, but make perfect sense when explained through real-world analogies. For example, pure functions are like pure math equations - they always give the same output for a given input and don't cause side effects. Immutability means data cannot be changed after creation, like how the number 3 will always be 3. Recursion is repeating a process, like looking up a word's definition using the dictionary definition. Higher order functions act on other functions, like how a phone case decorator wraps and enhances a phone. Laziness means waiting to compute results until needed, like only baking as many pastries as customers order.
Building Programs from Scratch
The best way to truly grasp FP is to start from scratch, without relying on libraries and frameworks. Let's build a simple program to sum an array of numbers using just basic language features. We take a recursive approach - break the array into head and tail, and calculate the head plus sum of the tail. The beauty here is how elegantly the concept translates into code using immutability, recursion and functions. Writing this way requires a shift in thinking, but feels very natural once it clicks.
function imperative_sum(numbers: number[]) {
const sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
function sum(numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
A Different Way of Thinking
FP forces you to approach problems differently compared to imperative programming. The focus on immutability means you can't just change state - you have to think in terms of transforming data. Recursion emphasizes breaking problems down into repetitive sub-problems. Pure functions make you think about inputs and outputs rather than instructions. It's a paradigm shift, but very rewarding. Solutions feel elegant, and you gain new perspectives. It improves programming intuition.
Classics like Fibonacci Recursively
Some recursive FP problems like calculating Fibonacci numbers are classics for good reason - they flex key skills. Let's implement a function to return the nth Fibonacci number. We'll use a recursive approach, where F(n) = F(n-1) + F(n-2). The base cases are F(0) = 0 and F(1) = 1. Then we call F(n) to trigger the recursion. Walking through the steps, we build up solutions by combining the results of smaller sub-problems, demonstrating recursive thinking elegantly.
function fib(n: number): number {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
console.log(fib(6)); // 8
Fun Puzzles to Flex Your Skills
What better way to solidify new concepts than fun puzzles? Let's try implementing a function that takes an array of words and returns only those longer than 5 characters. The FP way is to use higher order functions like filter rather than loops. We pass filter a predicate function that checks word length, and it returns a new array keeping only the long words. Readers can build their own versions for practice. Other challenges could include finding palindromes in arrays, or sorting numbers recursively.
// Function that returns true if word length > 5
const isLongWord = (word) => word.length > 5;
// Sample array of words
const words = ["sky", "water", "forest", "ocean"];
// Filter words using isLongWord as predicate
const longWords = words.filter(isLongWord);
console.log(longWords); // ['forest', 'ocean']
Higher Order Functions for Reuse
Higher order functions are the key to reuse and abstraction in FP. Let's demonstrate by implementing simple versions of the classics like Map, Filter and Reduce from scratch. We'll make them accept callback functions so they can act on any data. Reading their implementation helps demystify them. Now we can reuse these to operate on different arrays - Map to transform, Filter to select subsets, Reduce to combine down to a value. Higher order functions are powerful building blocks.
// Map
const map = (arr, callback) => {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
};
// Filter
const filter = (arr, callback) => {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
result.push(arr[i]);
}
}
return result;
};
// Reduce
const reduce = (arr, callback, initialValue) => {
let accumulator = initialValue;
for (let i = 0; i < arr.length; i++) {
accumulator = callback(accumulator, arr[i]);
}
return accumulator;
};
// Sample usage
const numbers = [1, 2, 3];
const doubled = map(numbers, (n) => n * 2);
const evens = filter(numbers, (n) => n % 2 === 0);
const sum = reduce(numbers, (acc, n) => acc + n, 0);
Lessons Learned the Hard Way
Learning FP involves some head-scratching moments and mistakes. I vividly remember struggling with avoiding mutable state and side effects early on. My impulse was to change a variable, when I needed to think immutably and return new data instead. I also got caught in infinite recursion a few times before I learned to identify base cases clearly. Readers will take comfort knowing that mastery comes through perseverance, and they'll learn from my slip-ups.
Compare and Contrast Languages
Since core FP principles are universal, it helps solidify knowledge by comparing implementations in different languages. For example, we could write a recursive Fibonacci function in Scheme, then contrast with Haskell and OCaml versions. They look similar due to pure recursion and immutability. But type safety in Haskell forces explicit type signatures. Laziness in Haskell also lets us memoize intermediate results elegantly. The variations highlight nuances while reinforcing fundamentals. We at TypeDriven prefer TypeScript because of the flexibility and ubiquity that JavaScript ecosystem allows us and the gradual typing that TypeScript brings to JavaScript. Furthermore we find the TypeScript type-system descriptive and expressive enough.
Hands-on and Interactive
The best way to learn is by doing. We'll make this hands-on with interactive code snippets using tools like CodePen. Readers can walk through implementations, tweaking them and observing results. We'll encourage them to try writing small functions themselves to cement concepts. There is no better way to internalize FP than by playing with code and gaining intuitive understanding through experience.
At TypeDriven we provide Coaching and Development services to get you moving in the right direction.