Learning functional programming improves all of your programming skills. It teaches you about general best practices for programming like inversion of control or the single responsibility principle. It also helps you write shorter, more reusable, and performant code. Not to mention that for many developers writing in a functional style is just more fun.
Functional programming is not a new thing. It is a declarative programming paradigm based on lambda calculus from 1930s. In 1937, Alan Turing himself proved that lambda calculus and Turing machines are equivalent computational systems. You don't need to learn any of the underlying mathematics but the math is there to make your coding easier. First functional programming language, LISP, was developed in 1950s. You can read about how LISP influenced early Yahoo. 70 years later, functional programming principles made their way into all major imperative object-oriented languages including Java, C#, or Python.
JavaScript through its support of first-class functions is ideal for you to take advantage of functional programming.
Improve your programming skills
It is important to know programming best practice to develop robust solution that will survive into the future. Following best practices has always required continuous study of programming as well discipline to apply in your code. Functional programming is different in a sense that it is designed to lead you towards those practices without you having to always be conscious and disciplined about it.
You might be surprised how effortless it becomes to shorter and faster code that is easy to understand and test.
Enjoy high-speed performance
Well designed functional code can be surprisingly easy to make super performant. We are building code based on pure functions. Pure functions always provide the same output for the same input. Composition is just many of these functions in a line and therefore the input at the beginning again returns the same output.
You can use this functional purity by leveraging caching mechanisms for memoization. Basically you map input of complex functions directly to their output skipping all the computation. With @7urtle/lambda it is super simple thanks to its function memo:
1import {memo} from '@7urtle/lambda';23const complexCompose = compose(lots of pure functions);45const memoCompose = memo(complexCompose);67memoCompose('input'); // executed once for the input8memoCompose('input'); // not executed, returns result directly
The same function memo will be also used in the functional programming example in the next section.
Write shorter code
In experience of functional programmers, functionally written code is generally much shorter. In JavaScript that's of course supported by a shorter syntax for arrow functions, but the more important difference is cause by better reusability and modularity of the code. In short, functional programming makes your code more clever and shorter.
For an example consider this advanced functional code:
1import {Maybe, liftA3, memo} from '@7urtle/lambda';23const getTargetOffsetTopMaybe = memo(input => Maybe.of(document.querySelector(input)).map(a => a.offsetTop));4const getClientHeightMaybe = memo(input => Maybe.of(document.querySelector(input)).map(a => a.clientHeight));5const getPositionMaybe = memo(input =>6liftA37(TargetOffsetTop => ArticleOffsetTop => MainMenuClientHeight => TargetOffsetTop + ArticleOffsetTop - MainMenuClientHeight - 28)8(getTargetOffsetTopMaybe(input))9(getTargetOffsetTopMaybe('article'))10(getClientHeightMaybe('.MainMenu')));1112const MaybePosition = getPositionMaybe('#someElement');13MaybePosition.isNothing() ? 'one of the elements may not exist' : 'position is ' + MaybePosition.value;
These 3 functions on 8 lines of code read the positions and sized of DOM elements on a page to calculate a position that would be later used for smooth scrolling animation. We are using monads Maybe because DOM represents a side effect and those elements simply may not exist. We use these monads as applicatives in liftA3 to safely calculate the position returning another Maybe monad. In the whole code that executes the animation we would not need to use any conditions or error handling because everything is elegantly taken care of by the magic of functional programming. As a bonus , because we don't want repeated calculation, the memo function takes care of caching, so each unique element is calculated only once.
Imperative code doing the same thing using more than three times as many lines to define only getPosition
would look like this:
1let ArticleOffsetTop = 0;2let MainMenuClientHeight = 0;3let cache = {};45const getPosition = function (input) {6if (input in cache) {7return cache[input];8}910const target = document.querySelector(input);1112if(ArticleOffsetTop === 0) {13const article = document.querySelector('article');14if(article !== undefined) {15ArticleOffsetTop = article.offsetTop;16}17}1819if(MainMenuClientHeight === 0) {20mainMenu = document.querySelector('.MainMenu');21if(mainMenu !== undefined) {22MainMenuClientHeight = mainMenu.clientHeight;23}24}2526if(target !== undefined && ArticleOffsetTop !== 0 && MainMenuClientHeight !== 0) {27const TargetOffsetTop = target.offsetTop;28const result = TargetOffsetTop + ArticleOffsetTop - MainMenuClientHeight - 28;29cache[input] = result;30return result;31} else {32cache[input] = undefined;33return undefined;34}35};3637const position = getPosition('#someElement');3839if(position === undefined) {40'one of the elements may not exist';41} else {42'position is ' + position;43}
KISS functional programming
KISS means Keep It Simple Stupid and it is a principle that says that simple code design should be always preferred. It is similar to the single responsibility principle that states: Every function should have responsibility over a single part of the functionality. One function should do one thing.
Functional programming helps you to write easily testable and highly reusable functions. It breaks your code into small simple pieces that you can compose into more and more powerful pieces. Because of that you will be able to understand your colleagues' as well as your own code more easily. At the same time it will be effortless to painlessly build test coverage.
If you goal was to write a function that always trims its input and turns it into upper case shouting, you would likely write it like this in imperative code:
1const shout = function (input) {2input = input.trim().toUpperCase();3const lastLetter = input.substr(-1);45if ('.?!,'.includes(lastLetter)) {6input = input.substr(0, input.length -1) + '!';7} else {8input = input + '!';9}1011return input;12};1314shout(" Don't forget to feed the turtle."); // => DON'T FORGET TO FEED THE TURTLE!
It is natural to focus on the overall task which is to build a function that does what we want and build a full function that delivers on the task. It takes discipline then to realize how to break the function down into its single responsibility parts.
This is also a reason why defensive programming is asking you to write a test for every step of your code. Large functions and classes have a tendency to hide a lot of internal logic if your unit test focuses only on a simple test of their output.
Now let's write the same function using @7urtle/lambda and functional programming principles of function composition.
1import {trim, upperCaseOf, lengthOf, lastLetterOf, includes, compose} from '@7urtle/lambda';23const endsWithPunctuation = input => includes(lastLetterOf(input))('.?,!');4const replacePunctuationWithExclamation = input => substr(lengthOf(input) - 1)(0)(input) + '!';5const addExclamationMark = input => endsWithPunctuation(input) ? replacePunctuationWithExclamation(input) : input + '!';67const shout = compose(addExclamationMark, upperCaseOf, trim);89shout(" Don't forget to feed the turtle."); // => DON'T FORGET TO FEED THE TURTLE!
The functional code follows the principle of single responsibility and we end up with four easily testable and highly reusable functions endsWithPunctuation
, replacePunctuationWithExclamation
, addExclamationMark
, and shout
. When you look at the function shout
, it is easy to deduce that the function return trimmed upper-case of its input with an exclamation mark. In the original code you have to read to whole function to understand what it does.
Inversion of control and dependency injection
In programming, it is easy to build code with tight dependencies and a mix of concerns that lead to solutions that are hard to cover with test and debug. Functional programming helps you avoid that.
Commonly you would load a module in your function or class to implement your functionality. Inversion of control turns that around so that your functions are designed to be executed in other modules. Dependency injection is a type of inversion of control. Dependency is an external code that your function is dependent on, and injection is the act of providing it to your function.
An example of a class without dependency injection would be:
1import {Database} from 'fictional-database';23class YourClass {4constructor () {5this.database = new Database().connect();6}7yourFunction () {8this.database.query();9}10}
Notice that database object instance is created withing the constructor
. The version with dependency injection would look like this:
1class MyClass {2constructor (database) {3this.database = database.connect();4}5myFunction () {6return this.database.query();7}8}
In this example, no database module is imported and no database instance is created. Instead MyClass
expects you to pass the database instance as a constructor
argument. This approach makes managing dependencies as well as testing much easier.
In functional programming, this is solved by the use of pure functions. Pure function is a function that depends only on its input without any side effects:
1let sideEffect = 1;23// impure example dependent on sideEffect4const impure = () => sideEffect + 1;56// pure example without any dependencies7const pure = input => input + 1;
Side effects that work with database are then managed through monads to create pure functions. This is a simplified example:
1import {SyncEffect, flatMap} from '@7urtle/lambda';23// pureGetUsers :: SyncEffect(Database) -> SyncEffect([a])4const pureGetUsers = flatMap(database => SyncEffect.of(database.query('SELECT * FROM users;')));56// Jest test7test('pureGetUsers returns list of users by calling database query from SyncEffect.', () => {8const DatabaseMock = SyncEffect.of(() => {query: () => ['user']})9expect(pureGetUsers(DatabaseMock).trigger()).toEqual(['user']);10});
Read more about testing in JavaScript with function programming.
Enjoy programming more
There are many stories of developers that have discovered functional programming and now they would never go back. Functional programming is fun because it allows you to focus on creating clever and elegant code that is easy to reason about.
Ask For Help
If you have any questions or need any help, please leave a message on @7urtle/lambda GitHub Issues to get quick assistance. If you find any errors in @7urtle/lambda or on the website please reach out.