We are familiar with functions as programmers, developers, software engineers, and <insert your favourite buzzword here>. We write programs composed of functions whose execution consists of evaluating functions. They are the basic building blocks of a program.
Although we write functions to create these magical programs and software, we may not write functions that are functional. By "functional" here, I don't mean they are not working, but rather that they're not fluent in the funky dialect of functional programming.
About Functional Programming
The basic idea of functional programming is writing programs which are declarative, avoiding changing-state and mutable data where the focus is on "what to solve" rather than the imperative approach of "how to solve". Below are some golden rules for writing functional functions.
The key charm of a pure function is that given the same input, it unfailingly produces the same output. The magic here is in writing programs that maintain their charm even when you replace a function with a value, i.e. Referential Transparency.
In this example, the
addToTotal function is impure because it relies on and modifies an external variable
total. Calling the function multiple times changes the external state, and the result depends on the order and number of calls. This can lead to unexpected behaviour and make it harder to reason about the program's behaviour. We can easily isolate and test
add function as it has no side effects.
The main principle for writing robust and error-free code is embracing immutability – never altering or updating data once it's created. In Typescript, we use
const to create immutable variables and avoid using
let keyword in our code unless it is absolutely necessary.
This may seem like a trivial mistake that will never happen in your code. However, such mishaps are far more likely to happen while working in large teams and creating complex software projects.
First-Class and Higher-Order Functions
First-class functions are functions that can be treated as variables and can be parsed to other functions as arguments. Typescript treats functions as first-class citizens. This means functions are simply a value and just another type of object.
In this example, the value
2 can easily replaced by the function
add(1,1) due to the function being treated as a value in Typescript.
Higher-order functions take one or more arguments and return a function as its result.
Using first-class and higher-order functions allows us to compose complex functions by combining simple functions, leading to more readable and reusable code. Using them, we can write functional, modular, concise code that focuses on "what to solve" rather than "how to solve".
In imperative and object-oriented programming, we are used to using loops like
for which introduces side effects that modify external states, creating a ripple effect throughout our code base.
In functional programming, we use recursion to solve problems that simply return the results without modifying global variables. Recursion allows us to tackle big problems into more breakable and manageable pieces, again focusing on "what to solve" rather than the imperative approach of "how to solve".
In this example, the recursive approach takes a more modular approach. The code is easier to understand and maintain and does not produce any side effects. While efficient in some cases, the imperative approach introduces a mutable state and is difficult to maintain.
Functional programming is more than just a set of rules and principles; it's a paradigm that transforms how we approach software development. It encourages us to embrace the beauty of declarative code, where the focus shifts from "how to solve" to "what to solve". It's a journey towards writing code that is functional, expressive, robust and maintainable.
Thank you for reading! Don't forget to leave a comment below.