@7urtle/lambda
JavaScript functional programming basics

JavaScript is an amazing programming language that rules the internet. No modern web application is done without it. JavaScript is a multiparadigmatic language that supports both object-oriented and functional approach to programming.

Functions to rule everything

Functional programming is based on the use of functions and functors. Since ES6, JavaScript offers arrow functions for concise lambda expressions:

1
const myFunction = value => 'I am ' + value;
2
3
myFunction('a turtle'); // => 'I am a turtle';

Which is equivalent to:

1
const myFunction = function (value) {
2
const result = "I am " + value;
3
return result;
4
};
5
6
myFunction('a turtle'); // => 'I am a turtle';

To follow functional programming practices, you should understand pure functions, higher-order functions, partial application, and function composition.

Pure functions

A pure function is a function in which output depends only on its input without any side effects. Side effect is any change of variables outside of the function's scope (in functional programming that is called mutation). Side effect is also any reading or writing into IO like console log, file reading, database updating and so on.

In functional programming, side effects are managed through monads. Monads make working with side effects more explicit and safe while ensuring general programming best practices.

Side effects, like reading variables outside of functional scope, may not feel particular dangerous to you. Code like this is quite common:

1
let tortoises = 'Leonardo';
2
3
const addDonatelo = function () {
4
tortoises = tortoises + ' and Donatelo';
5
};
6
7
addDonatelo(); // => tortoises is 'Leonardo and Donatelo'
8
tortoises = tortoises.toUpperCase();
9
addDonatelo(); // => tortoises is 'LEONARDO AND DONATELO and Donatelo

addDonatelo in the example depends on tortoises variable which is outside of its scope. The danger lies in the fact that any part of the code can change the value of counter, not just your function. That means that it is impossible to know the effect of addDonatelo without knowing the current value of tortoises. At the same time addDonatelo is also harder to test because you have to mock all your dependencies for any unit test of a function. In a real application such dependencies are often very complicated and lead to unpredictable code that is hard to test.

Functional code is much easier to reason about because pure functions are dependent only on their input.

1
const addDonatelo = tortoises => tortoises + ' and Donatelo';
2
addDonatelo('Leonardo'); // => 'Leonardo and Donatelo'
3
addDonatelo('Leonardo'); // => 'Leonardo and Donatelo'

addDonatelo in the functional example is a pure function that depends only on its input. You can always easily say what it's output is going to be.

Functions in a functional programming are declarative lambda expressions that represent a mapping from their inputs to its outputs. That is a very important principle.

JavaScript itself is quite unpredictable because some of its functions are pure and some are not.

1
let me = 'Martin';
2
let us = ['Petra', 'Martin'];
3
4
let newMe = me.toUpperCase();
5
let newUs = us.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
6
7
me === newMe; // false, toUpperCase is pure, it did not mutate the me variable
8
us === newUs; // true, sort is impure, it mutated the us variable

In @7urtle/lambda functions are made predictable and pure.

1
import {upperCaseOf, sort} from '@7urtle/lambda';
2
3
let me = 'Martin';
4
let us = ['Petra', 'Martin'];
5
6
let newMe = upperCaseOf(me);
7
let newUs = sort((a, b) => a < b ? -1 : a > b ? 1 : 0)(us);
8
9
me === newMe; // false, upperCaseOf is pure, it did not mutate the me variable
10
us === newUs; // false, sort is now also pure, it did not mutate the us variable

Because functional programming works with pure functions, all variables are immutable constants. In practice, in JavaScript, that means in your code you should not need to change any variables. All code logic is expressed by functions mapping inputs to outputs.

First-class and higher-order functions

JavaScript has first-class functions because functions are treated like any other value. A function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable.

Higher-order function is a function that takes a function as its input or it returns a function as its output. It can be obvious to you, because in JavaScript you regularly assign your functions into variables.

1
const myFunction = function (value) {
2
return value + 1;
3
};
4
const myArrowFunction = value => value + 1;
5
// both myFunction and myArrowFunction are pure functions saved in a constant variable
6
7
const myFirstClassFunction = function (fn, value) {
8
return fn(value);
9
};
10
const myFirstClassArrowFunction = (fn, value) => fn(value);
11
// both myFirstClassFunction and myFirstClassArrowFunction expect their first argument fn
12
// to be a function
13
14
const myCurriedFunction = function (fn) {
15
return function (value) {
16
return fn(value);
17
};
18
};
19
const myCurriedArrowFunction = fn => value => fn(value);
20
// both myCurriedFunction and myCurriedArrowFunction are curried functions that return
21
// a function after receiving their first argument.
22
23
myCurriedFunction(myFunction)(1) === myCurriedArrowFunction(myFunction)(1); // true

The use of higher-order functions is instrumental to currying, partial application, and function composition.

You have been using higher-order functions all along when you were assigning functions as arguments for click button events:

1
document.querySelector('#myButton').addEventListner('click', function() {
2
alert('click happened inside higher-order function');
3
});

Partial Application

When defining functions that depend on multiple arguments, you would usually do something like this:

1
const add = function (a, b) {
2
return a + b;
3
};
4
5
add(1, 2); // => 3

In functional programming, the best practice is to define all your functions curried. That means that after your function is called with its first argument it returns a function expecting the next argument until all arguments are received. This approach helps with reusability of your code as your function will lend themselves nicely to be combined into other functions through partial application and function composition.

1
const add = a => b => a + b;
2
3
add(1)(2); // => 3
4
5
const add5 = b => add(5)(b);
6
const add5PointFree = add(5);
7
8
add5(5) + add5PointFree(5); // 20

In @7urtle/lambda functions support being called as both curried unary or common n-ary functions so that you can adopt the style that you choose.

1
import {substr} from '@7urtle/lambda';
2
3
'7urtle'.substr(0, 1); // => '7', original JavaScript
4
substr(1)(0)('7urtle'); // => '7'
5
substr(1, 0, '7urtle'); // => '7'

You may also notice that @7urtle/lambda follows a specific logic for order of function inputs to make all functions ready for function composition.

Function composition

Classes in object-oriented programming allow you to organize your code into logical models of the world. Instances of classes have their own scope, you can inherit methods and so on. Functional programming doesn't need classes, instead we model our code by flexible function composition.

Pure functions just map inputs into outputs and we then take those outputs and use them as inputs for our next function in the row. This way simple functions come together to build complex logic.

As an example we will build a functional magic that will take a list of names as its input and it will return an upper-case string of those names alphabetically ordered. Let's do it first imperatively without @7urtle/lambda:

1
const names = ['Petra', 'luci', 'MARTIN'];
2
3
const sortingAZ = function (ao, bo) {
4
const a = ao.toUpperCase();
5
const b = bo.toUpperCase();
6
7
if (a < b) {
8
return -1;
9
}
10
11
if (a > b) {
12
return 1;
13
} else {
14
return 0;
15
}
16
};
17
18
const magic = function (names) {
19
const sorted = names.sort(sortingAZ);
20
21
let result = '';
22
sorted.forEach(function (name) {
23
result = result + name + ' ';
24
});
25
26
return result.trim().toUpperCase();
27
};
28
29
magic(names); // LUCI MARTIN PETRA

Now let's do the same declaratively using function composition and @7urtle/lambda:

1
import {reduce, sortAlphabetically, upperCaseOf, compose} from '@7urtle/lambda';
2
3
const names = ['Petra', 'luci', 'MARTIN'];
4
5
const justString = list => reduce('')((a, c) => a + ' ' + c)(list);
6
7
const magic = compose(upperCaseOf, justString, sortAlphabetically);
8
// the same as const magic = value => upperCaseOf(justString(sortAlphabetically(value)));
9
10
magic(names); // LUCI MARTIN PETRA

You can easily see how @7urtle/lambda and functional programming made that task a child's play by defining just two functions on two lines of code. Once you get familiar with functional programming syntax, you will be able to write much shorter, reusable, and robust code that follows general programming best practices. Look at more functional programming advantages.

Function composition based on pure functions can also enjoy the advantages of referential transparency. In functional programming, referential transparency is defined as the fact that a pure function (including composed pure functions) may be replaced by its value without changing the result of the program. That has an actual practical impact because you can use memoization and significantly improve the performance of you code by replacing the results of functions through cached values. @7urtle/lambda for that purpose offers a very practical function memo.

Argumentless tacit point-free programming

In functional programming it is typical to use of argumentless tacit (point-free style) of code. It was also used in the previous code example with the function compose.Tacit code allows you to omit explicit declaration of arguments. An example can be see here:

1
import {map} from '@7urtle/lambda';
2
3
const names = ['Petra', 'luci', 'MARTIN'];
4
5
// regular declaration with argument list present
6
const upperCaseOfArray = list => map(upperCaseOf)(list);
7
8
// tacit point-free declaration valid in JavaScript
9
const upperCaseOfArrayTacit = map(upperCaseOf);
10
11
upperCaseOfArrayTacit(names); // ['PETRA', 'LUCI', 'MARTIN'];

All your curried functions (and all @7urtle/lambda functions) can be declared this way. It makes your code much more concise when you get used to it and you can see many example of tacit declarations elsewhere in Learn articles. It is useful especially with partial application. The example of upperCaseOfArrayTacit is an example of partial application of the function map.

Unfortunately there are some disadvantages. Practical disadvantage is a low support in IDEs that won't recognize your function as a function and instead it will consider it a variable with a value which leads to wrong coloring (but no errors). You can see the same issue in the code example above. On the other hand, JavaScript has first-class functions, so in the end the function is a value and you might say that the color is actually correct.

Additional disadvantage is that your colleagues may have hard time understanding the code if they are not familiar with tacit programming or the functions that you use. Because of that it should be recommended that you are careful in your use of point-free style. Also you may consider using simple Hindley-Milner inspired comments that would show the function structure to anyone reading the code. The same principle is also used in the @7urtle/lambda source code.

1
import {includes, filter} from '@7urtle/lambda';
2
3
const animals = ['Hermann\'s Tortoise', 'Dog', 'Russian Tortoise', 'Cat', 'Greek Tortoise'];
4
5
// isTortoise :: string -> boolean
6
const isTortoise = includes('Tortoise');
7
8
/**
9
* @HindleyMilner tortoisesFromArray :: [string] -> [string]
10
*/
11
const tortoisesFromArray = filter(isTortoise);
12
13
tortoisesFromArray(animals);
14
// => ['Hermann's Tortoise', 'Russian Tortoise', 'Greek Tortoise']

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.

Get Started

Install @7urtle/lambda with NPM or add it directly to your website.