Take Your Code to the Next Level with FKit
October 13, 2014
Recently I’ve been busy working on FKit: a functional programming toolkit for JavaScript. It provides many functions for solving common problems with functions, objects, arrays, and strings.
I wrote FKit because I believe that JavaScript is lacking the kind of standard
library that many other languages benefit from. A good example is the prelude
library in Haskell: it provides developers with an amazing toolbox to write
their Haskell applications with. Why should JavaScript developers have to
bother solving these common problems over and over again?
Other libraries have attempted to address this issue (Underscore, Lo-dash, etc), but in my opinion they’re rudimentary. The implication is that you often have to ‘reinvent the wheel’ while writing JavaScript day-to-day.
FKit is my attempt at a standard library for JavaScript. It aims to provide reusable building blocks while maintaining a laser focus on everyday utility. This article explains some of the core features of FKit and gives some background to many of the functional programming concepts it uses.
Curried Functions
Most functions in FKit are already curried by default, so it’s worth spending some time explaining curried functions.
A curried function is a function that instead of taking multiple arguments takes exactly one argument. It returns another function that takes exactly one argument, and so on. When all the arguments are specified, the result is returned.
To illustrate this concept let’s define a curried function add
that simply
adds two numbers together. It takes a value a
and returns another function
that takes a value b
. When add
is applied to the values a
and b
then
the result a + b
is returned:
function add(a) {
return function (b) {
return a + b;
};
}
add(1)(2); // 3
With FKit you can easily curry a function of any
arity using the curry
function:
import { curry } from "fkit";
const add3 = curry((a, b, c) => a + b + c);
add3(1)(2)(3); // 6
Partially Applied Functions
What happens when we don’t specify all the arguments to a curried function? Let’s find out.
First, let’s define a curried function mul
that multiplies two numbers
together:
function mul(a) {
return function (b) {
return a * b;
};
}
mul(1)(2); // 2
When we apply mul
to one value only, we get what is known as a partially
applied function instead the result a * b
:
mul(2); // function
Here the mul
function has been partially applied to the value 2
. This
results in another function which simply multiplies a given number by 2
in
other words, it doubles a given number.
This is actually quite useful. For example, we can assign the partially applied
mul
function to the variable double
and apply it to some other values:
const double = mul(2);
double(1); // 2
double(2); // 4
double(3); // 6
Many of the functions in FKit can be partially applied to expressively and elegantly solve problems. Let’s take a look at a few examples using FKit.
Here we multiply each number in a list by two:
import { mul } from "fkit";
[1, 2, 3].map(mul(2)); // [2, 4, 6]
By using the reduce
function with add
(a binary function), we can sum all
the numbers in a list:
import { add } from "fkit";
[1, 2, 3].reduce(add); // 6
To filter the all the numbers in a list which are greater than one, we can use
the gt
predicate:
import { gt } from "fkit";
[1, 2, 3].filter(gt(1)); // [2, 3]
Function Composition
Function composition is a powerful way to specify a series of functions, where
each function takes the result of another function as an argument. In other
words, compose(f, g)
is equivalent to saying f(g(a))
.
Now let’s define a function compose
that composes two functions together:
function compose(f, g) {
return function (a) {
return f(g(a));
};
}
Using our add
and mul
functions from earlier, we can compose a function
doubleAndAddOne
that doubles and adds one to a given number:
const doubleAndAddOne = compose(add(1), mul(2));
doubleAndAddOne(1); // 3
doubleAndAddOne(2); // 5
doubleAndAddOne(3); // 7
FKit allows you to easily compose any number of functions together using the
compose
function:
import { compose } from "fkit";
const myFunction = compose(f, g, h);
Lists
Have you ever wondered why you can call reverse
on an array, but not on a
string?
[1, 2, 3].reverse(); // [3, 2, 1]
"foo".reverse(); // TypeError: Object foo has no method 'reverse'.
This is because arrays and strings are fundamentally different data types in JavaScript. Other languages don’t make this distinction after all, a string is really just a list of characters. Imagine how useful it would be to use the same functions on different kinds of lists, whether it be an array of numbers or a string?
FKit provides many list functions and they all work in exactly the same way for arrays as they do for strings. This seemingly simple abstraction is very powerful.
To highlight this point, let’s look at some basic list functions on both arrays and strings:
import { head, init, last, tail } from "fkit";
head([1, 2, 3]); // 1
head("foo"); // 'f'
tail([1, 2, 3]); // [2, 3]
tail("foo"); // 'oo'
last([1, 2, 3]); // 3
last("foo"); // 'o'
init([1, 2, 3]); // [1, 2]
init("foo"); // 'fo'
We can also create new lists by adding elements to lists:
import { append, intersperse, prepend, surround } from "fkit";
append(3, [1, 2]); // [1, 2, 3]
append("o", "fo"); // 'foo'
prepend(1, [2, 3]); // [1, 2, 3]
prepend("f", "oo"); // 'foo'
surround(0, 4, [1, 2, 3]); // [0, 1, 2, 3, 4]
surround("¡", "!", "hola"); // '¡hola!'
intersperse(4, [1, 2, 3]); // [1, 4, 2, 4, 3]
intersperse("-", "foo"); // 'f-o-o'
Joining lists together? No problemmo:
import { concat } from "fkit";
concat([1], [2, 3], [4, 5, 6]); // [1, 2, 3, 4, 5, 6]
concat("f", "oo", "bar"); // 'foobar'
Building new lists? It’s a snack:
import { replicate } from "fkit";
replicate(1, 3); // [1, 1, 1]
replicate("a", 3); // 'aaa'
Combinators
JavaScript provides several combinators for working with arrays, for example
map
, filter
and reduce
. Combinators are higher-order functions (functions
that takes other functions as arguments) that iterate over arrays – or more
generally, any data structure – in some way.
FKit also provides all of your favourite combinators, plus a few more you may not have encountered before. They also all work on both strings and arrays!
map
takes a function and applies it to every element in a list, returning a
new list:
import { inc, map, toUpper } from "fkit";
map(inc, [1, 2, 3]); // [2, 3, 4]
map(toUpper, "foo"); // ['F', 'O', 'O']
filter
filters the elements in a list using a predicate function:
import { eq, filter, gt } from "fkit";
filter(gt(1), [1, 2, 3]); // [2, 3]
filter(eq("o"), "foo"); // 'oo'
fold
folds a list into a single value using a binary function and a
starting value:
import { add, flip, fold, prepend } from "fkit";
fold(add, 0, [1, 2, 3]); // 6
fold(flip(prepend), "", "foo"); // 'oof'
zip
zips the corresponding elements from two lists into a list of pairs:
import { zip } from "fkit";
zip([1, 2, 3], [4, 5, 6]); // [[1, 4], [2, 5], [3, 6]]
zip("foo", "bar"); // [['f', 'b'], ['o', 'a'], ['o', 'r']]
concatMap
maps a function that returns a list over every element in a list,
and concatenates the results:
import { concatMap } from "fkit";
function p(a) {
return [a, 0];
}
concatMap(p, [1, 2, 3]); // [1, 0, 2, 0, 3, 0]
function q(a) {
return a + "-";
}
concatMap(q, "foo"); // 'f-o-o-'
Immutable Objects
Even though it leads to much simpler application development, JavaScript doesn’t enforce any constraints on the immutability of objects. Developers tend to solve problems by mutating the state of their objects, leading to hard-to-find bugs.
FKit provides several functions to make it easy to work with immutable objects. To begin the next example, let’s define a list of shapes:
const shapes = [
{ type: "circle", colour: "red", size: 1 },
{ type: "square", colour: "green", size: 2 },
{ type: "triangle", colour: "blue", size: 3 },
];
If we want to get the colour of each shape we can use the get
function:
import { get } from "fkit";
shapes.map(get("colour")); // ['red', 'green', 'blue']
What if we want to change a property of the shapes? We can use the set
function to do that:
import { set } from "fkit";
shapes.map(set("size", 100)); // [{..., size: 100}, {..., size: 100}, {..., size: 100}]
Importantly, in the above example the original shapes remain unchanged. FKit
makes a copy of each of the shapes and sets the size
property to 100
. FKit
will also respect the prototype of the original object and ensure that the copy
has the same prototype as the original.
Learn Some More
Hopefully by now you’ve learned something about FKit and some of the functional programming concepts behind it. What you’ve seen in this article is only the tip of the iceberg.
I encourage you to take a look at the FKit project on GitHub and read through the API docs if you want to learn more.
Credits
The images in this article are borrowed from the wonderful book Learn You a Haskell for Great Good by Miran Lipovača.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.