paint-brush
Writing functional javascript without a 3rd party library.by@docodemore
1,733 reads
1,733 reads

Writing functional javascript without a 3rd party library.

by Matt McFarlandNovember 7th, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

About a year ago, some colleagues and I paired up on creating our own functions instead of using a library to keep the bundle size down. While one option is to use <a href="https://webpack.js.org/guides/tree-shaking/" target="_blank">tree-shaking</a>, we figured we could reduce it even further by doing away with some of the scaffolding and boilerplate that comes along with it, and I had an idea I really wanted to try out. This is the story of how I experimented with native Javascript and flipped it inside out to a more functional paradigm, and had a functional mini-library of around 500 bytes.

Company Mentioned

Mention Thumbnail
featured image - Writing functional javascript without a 3rd party library.
Matt McFarland HackerNoon profile picture

About a year ago, some colleagues and I paired up on creating our own functions instead of using a library to keep the bundle size down. While one option is to use tree-shaking, we figured we could reduce it even further by doing away with some of the scaffolding and boilerplate that comes along with it, and I had an idea I really wanted to try out. This is the story of how I experimented with native Javascript and flipped it inside out to a more functional paradigm, and had a functional mini-library of around 500 bytes.

Its starts with a scenario in which I dream up many of my ideas, while I was driving home. As I traveled down the interstate the thought occurred to me, “What if we created an Object that just intrinsically used the already existing Array methods, but it had currying and could use pipe and compose!”

I mean, all of the common functions are widely available — like map, reduce, filter, and so on. The only thing I really wanted to do is to somehow “borrow” their already optimized functionality, not rewrite them.

I also figured that the only functions that we didn’t have that I really wanted and to make it all work would be pipe and compose — which would be really the only “non-native” functions in our toolbox for this experiment.

Creating the “whitelist”

Since Javascript’s Array object has all of the functions I needed, I figured I could pluck them out. To do that, I created a “whitelist” of functions I wanted from the Array Object:

const whitelist = ['concat', 'every', 'filter ', 'find ', 'includes', 'keys', 'map', 'reduce ', 'reduceRight', 'slice', 'some']

Alright! So after perusing the MDN docs, and paying no mind at all to browser compatibility (at least not YET anyway!) I sought to “invert” (maybe wrong term here) the functions so that they would be curry-able, so you could do stuff with them using a function like compose or pipe

The trick was to create a new Object, and plop the Array’s prototype methods directly onto it. The first name I came up with (which has been renamed a few times since) was Box which was inspired by Dr. Boolean’s series on egghead

It would also use getOwnPropertyNames from Array.prototype, see if any of the methods are available on the prototype that match the whitelist, and if they were there, make them part of theBox Object.

This would allow for unsupported methods to just be as they already are: undefined — So if someone wanted to consume this Object, they were not getting anything really new, just a different way of using the same functions they already have. If you needed some additional browser support you could just add the polyfill and then add the Box — which I just thought was a neat idea to go about it!







const Box = {}Object.getOwnPropertyNames(Array.prototype).filter(s => whitelist.includes(s)).forEach(method => {Box[method] = fn => a => a[method](fn)})

So now the only thing left was to add pipe and compose:

(special thanks to Ronn Ross for writing these handy ones)


Box.compose = (...fns) => data => fns.reduce((value, fn) => fn(value), data)[0]Box.pipe = (...fns) => data => fns.reverse().reduce((value, fn) => fn(value), data)

module.exports = Box

Alrighty, so to put this entire functional powerpack together, at a very tiny size of something around 500 bytes you have:


// Let's pluck these methods off of Array.prototypeconst whitelist = ['concat', 'every', 'filter ', 'find ', 'includes', 'keys', 'map', 'reduce ', 'reduceRight', 'slice', 'some']


// We'll put them on a container Objectconst Box = {}







// So if any of the methods exist, we add them, if not, we just move on.Object.getOwnPropertyNames(Array.prototype).filter(s => whitelist.includes(s)).forEach(method => {Box[method] = fn => a => a[method](fn)})



// We'll create two functions that are our own for making things compositionalBox.compose = (...fns) => data => fns.reduce((value, fn) => fn(value), data)[0]Box.pipe = (...fns) => data => fns.reverse().reduce((value, fn) => fn(value), data)

module.exports = Box

Pretty cool! So how do you use the compose and pipe functions?

here’s an example using compose











compose(// 42reduce((acc, val) => val + acc),// [12, 14, 16]map(x => x * 2),// [6, 7, 8]filter(x => x > 5),// [0, 1, 2, 3, 4, 5, 6, 7, 8],concat([6, 7, 8])// [0, 1, 2, 3, 4, 5])([1, 2, 3, 4, 5])

So that contrived example creates a function that we pass an array of [1, 2, 3, 4, 5] into it. The function then passes the arguments along from right to left (as compose works) — resulting in 42 :)

If we wanted to read it from left to right, we could use pipe:








pipe(map(x => x + 1),// [1]map(x => x + 1),// [2]map(x => x + 1),// [3])([0])

Like the compose method, this pipe method creates a function that takes the paramater [0], which is then *piped* from left to right, incrementing it by one as it goes.

You could do some fancier things with this, and you dont necessarily need to use pipe and compose, you could make a function called addTwo which is basically a curried function that adds two to the parameter, again using our functionalized array methods:



const arrayOne = [1, 2, 3];const addTwo = concat([4, 5])const result = (addTwo(arrayOne))


console.log(result)// [1, 2, 3, 4, 5]

I’m hoping someone finds this useful, because you really can do functional programming with javascript without a library!

That being said, a library gives you extra functionality, and backwards compatibile support. You can even use tree-shaking to reduce the bundlesize, so I can’t say it enough, if you want to use a library please do so. But, you just might find this interesting!

We did end up turning this experiment into a library, and iterated over it quite a bit, fixing bugs/etc. We also added string support. The library we ended up with is called pico-lambda, check it out (especially its source code, its tiny!), it also has a ton of unit tests and they run in browserstack across the major browsers too.

After it was all said and done, and we worked out edge cases and fixed bugs, added strings to the mix, we ended up with a slightly bigger “Box”















Object.getOwnPropertyNames(Array.prototype).reduce((lambda, method) => {lambda[method] = (~['concat', 'every', 'filter', 'find', 'findIndex', 'includes', 'join', 'map', 'reduce', 'reduceRight', 'slice', 'some'].indexOf(method))? (fn, ...params) => arr => arr[method](fn, ...params): (~['sort', 'copyWithin', 'fill'].indexOf(method))? (...params) => arr => [...arr][method](...params): (~['toLocaleString', 'indexOf', 'lastIndexOf'].indexOf(method))? (...params) => arr => arr[method](...params): (~['push', 'splice'].indexOf(method))? (...params) => arr => { var t = [...arr]; t[method](...params); return t; }: (~['toString', 'entries', 'keys'].indexOf(method))? arr => arr[method](): lambda[method];...full source here

Thanks for reading, if you like this please clap and follow me. Also leave a comment if you’d like to share your thoughts or have any questions!