Using ES6 to create a tiny, functional library

The code related to this post: https://github.com/dvemac/tinyfun

Functional programming allows us to craft applications using functions as building blocks. Wiring simpler functions together to define more complex functions & business logic. Because of its declarative, side-effect free nature code is easier to reason about, easier to test, becomes more readable and you become more productive (probably).

What are pure functions?

A pure function is a function where the return value is only determined by its input values, it will always evaluate to the same result value given the same arguments — we can call this function side-effect free. A much more in-depth explanation can be found on Wikipedia.

function add10(num) { return num + 10 }  // Pure!
var count = 0
function addNum(num) { count = count + num } // Impure :(

The Goal

JavaScript in many ways can be considered a functional language (depending on your definition of functional) so, inspired by Ramda — if you haven’t seen Ramda I highly recommend you check it out — we are going to use new functionality of ES6 to create a tiny functional library, hopefully less than 1kB.


Setup

First up we are going to alias a few Array methods for easier access, but also the explicit use of the prototype faster because it requires less work for the run-time to calculate where the method lives.

Here we take advantage of ES6 destructuring assignment to assign various Array.prototype methods to prefixed variables e.g. _slice = Array.prototype.slice


Basic building blocks

Next we can create some basic utility functions

The function signatures in the comments are the same as those from the Ramda docs.

We can use the ES6 arrow functions to tersely define functions that return functions — these are called Higher-order functions

Higher-Order Functions are functions that take functions as arguments and/or return functions as values.

We could also write the equals function in the following, more verbose way:

Executing const isEqualTo3 = equals(3) will return a new function that takes one argument. We can then call isEqualTo3(4) which, of course, will return false.

Both the verbose and terse implementations behave in exactly the same way (with the exception of scope binding — which is another story).


Building functions with functions

Then we can start to use our building blocks to build out other functions

Notice here how we use the slice function to create the init and tail functions. Because slice is a Higher-order function we can call it with some parameters (s and e, for start and end respectively) and it will return a new function that accepts an array (the as parameter — an array of things).

So, when we assign init = slice(0, -1) it will create a function that accepts an array and returns all but the last element — the initial part of the array. init([1,2,3]) will return[1,2].

You may have spotted compose in there, we’ll come to that later.


Recursive function invocation & spread syntax

We can continue to use our building blocks and array function aliases to create further blocks

We define several functions (concat, sort, map, filter) which just re-arrange the parameter order of the Array.prototype functions to make them more functional.

Then we use the ES6 spread syntax in the concat function to collect all functions’ arguments into an array.

The spread syntax allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables (for destructuring assignment) are expected.

So if we call concat(1,2,3) the value of as will be [1,2,3] — an array of all the arguments passed.


flatten takes advantage of previously defined functions by using reduce and concat but is also a recursive function. It calls itself when the supplied argument is another array. This means it can flatten even deeply nested arrays of arrays, such as [1,2,[3,[4,5,6]]] to [1,2,3,4,5,6].

Recursion: An act of a function calling itself. Recursion is used to solve problems that contain smaller sub-problems. A recursive function can receive two inputs: a base case (ends recursion) or a recursive case (continues recursion).

Compose — the Human Centipede of functional programming

Function composition is a very powerful concept, it allows us to combine simple functions to create more complicated ones.

The compose function takes one or more functions as arguments and returns a new function. When this new function is called it passes any given arguments into the composed functions from right to left. The result of the right-most function is passed into the next, and so on.

Our compose implementation uses the ES6 spread syntax and is also a recursive function as it calls itself when more than one argument is passed.

The composition

The any function accepts the parameter f which is a function that returns a boolean. This function is provided to filter and is used to decide if any of the values provided match the criteria. An example function for f might be:

x => x.length > 3

Three functions are composed together to create any: filter, length and Boolean. First the values are run through the filter which uses the supplied f function. length then returns a number — how many items where returned from filter, then we use the native Boolean method to turn the result into either true or false, where zero converts to false and any other number returns true.

Pop quiz, what will be the result of calling someFunc({ name: 'fred' }) based on the following code snippet:

const getName = prop(‘name’)
const someFunc = compose(equals(3), length, getName)

Utilizing ES6 Set constructor

ES6 introduced the Set object that lets you store unique values of any type, whether primitive or object references. We can make use of this behavior to create a few more functions for our library.

First we define uniq which, as the name suggests, takes an array of things and returns a de-duplicated version. The supplied value as is passed to the Set constructor (which accepts any iterable object type) and will add non-duplicate values to the set, then we use the ES6 destructurting syntax to convert the set back into an array.

Array.isArray(new Set())  ===  false
Array.isArray([...new Set()]) === true

It is worth noting the equality test used by Set is not the same as ===. In a Set NaN is equal to NaN, where-as outside of a set NaN === NaN is false. So you can only ever have one occurance of NaN in a set.

We get the union function for free as it is just a composition of concat and uniq — from the Ramda docs: Combines two lists into a set (i.e. no duplicates) composed of the elements of each list.


Fin

This is by no means an exhaustive library and I’m sure there are many more tricks that could be employed and functions to be implemented. The complete library rocks in at ~700bytes (gzipped), so there is plenty of space before we hit 1kB.

Because of the cost of creating function in JavaScript I expect the performance of the library to be pretty bad compared to a more imperative implementation. At some point I will benchmark to see.

Please feel free to call out any errors in this post or bugs in the library.