Practical Functional JavaScript

March 29, 2018 0 Comments

Practical Functional JavaScript

 

 

After working for serveral years with CMSs systems, I got very familiar with the idea that sophisticated components abstraction and the ability to plug in new functionality on existing software were some of the best signals of a good software architecture.

In CMSs we often need to work on new tools for enabling administrators to quickly change site behaviours to meet a specific business need and we often need to abstract it in frameworks and subsystems, making modules that are flexible enough to cover most use cases we can think of.

When I started working with modern frontend JavaScript libraries such as React and Redux, I got in contact with some functional programming concepts that changed the way I think about writing clean code and promote code reusing: I've discovered the power of decoupled functions composition.

One simply, killer advantages of functional programming that really stands out is writing applications with less code. Less code means less places for bugs to hide and less code to maintain. Also, small decoupled functions are easy to unit test. Lets see in practice how it can help us writing better, maintainable code.

First lets take a look on some very simple concepts that are fundamental for writing nice decoupled functions that can be composed to form a big function that actually does the thing.

The most basic and simple concept you need to embrace is pure functions. Pure functions are functions that given the same set of parameters always have the same return. That's not the same as having the same result. For writing composable software you must whenever you can avoid producing side-effects. The most simple example of a pure function, is the identity function:

const f = a => a

One thing that's intrinsic to pure functions, is referential transparency, that is, for a function to remain pure, it cannot use data from outside its scope, in other words, it can only rely on data provided through its own arguments. Also it can't mutate or call a method that does so, performing some side-effect in the application. We need to avoid such side-effects, since this can lead to unknown results that will interfere in the result of the function, making it non deterministic.

In essence a Higher-order Function or a HoF is a function that returns another function. By doing so, it creates a “context” where its returned function resides. You can also think it as a state that now the returned function have in its runtime incarnation. This ability to create an internal state for a function as a way to configure it to prepare it to be used solo or in a composition.

const say = a => {
return b => {
return ${a} ${b}
}
}
say('hello')('world')
// hello world

With ES6 arrow functions, this can be written in a much more expressive manner:

const say => a => b => ${a} ${b}

Currying is taking a function that have n arguments (arity n) and refactoring or transforming it to be multiple functions that takes 1 to n arguments. When we transform a function that has, say, two parameters, into two nested functions (the first function returning the second one), we can create several, or in some cases, infinity derivate functions by partially applying or “configuring” the set.

Let illustrate this for better understanding:

const sum = (a, b) => a + b
sum(1, 2)
// 3

It’s very common for a function to take n arguments, but there are advantages on writing functions that accepts only one argument too:

const sum = a => b => a + b
sum(1)(2)
// 3

One benefit of HoFs and curried functions in general is the possibility to do a partial application to that function, and generate several others:

const sumTen = sum(10)
sumTen(20)
// 30

Now you have a new function for you arsenal that was originated from a partial application on the original function. This is a not-so-super useful one for the most real world applications need, but it’s just to illustrate the idea. When you call sum() now with the only argument it takes, it's kind like if you were configuring it to have some kind of "internal state" that now is set to 10.

But why is this useful? Were we not talking about function composition?

Yes, with just one curried/higher-order function we can already compose.

h(x) = f(g(x))

In a classical composition, functions are composed from right to left, that means g() is called first, then f() is called with the result of g().

const sumTen = sum(10)
const subtractFive = sum(-5)
const calculate = x => subtractFive(sumTen(x))
calculate(10)
// 15

Done. We now have partially applied sum() twice, generating two new functions sumTen() and subtractFive() that we've composed to generate a third one calculate() . These codes are just obvious code to exercise the concepts, they don't really do anything like what we need in our daily programmer routines.

When we talk about function composition, we are talking about a group of chained functions called one after the other, and that takes only one argument each: the return of its predecessor. Since functions conceptually can only return one thing at a time, this is actually very natural.

This is good, but we can do better. Up to two functions is ok composing this way (calling a function as the argument of the other), but more than that, the code starts to look weird and either the line gets too long or you have a weird “Hadouken Code” like this:

f(
e(
d(
c(
b(
a(x)
)
)
)
)
)

To improve how we compose functions, we can use libraries like Ramda, or write our own tools ourselves to deal with it. Here’s a simple implementation of a compose() function that help us write compositions in a more readable way:

const compose = (...fns) =>
fns.reduce((f, g) => (...args) => f(g(...args)))

With this compose() function, that takes multiple unary functions as arguments, we could rewrite that “Hadouken Code” like this:

compose(f, e, d, c, b, a)

As you can see, this compose() function only runs through an array of functions that are taken from its arguments, calling each one, passing the result of the previous one and then return a new function, that when called, the function more on the right, will be called with that argument, triggering the calling of the functions more to the left.

Classical composition is cool, but we look at things from left to right while coding. Look at how we would rewrite thatcalculate() function from the start of this section:

const calculate = compose(subtractFive, sumTen)

What this is really doing is first summing ten than subtracting five, not the opposite. Here is where the pipe() pattern comes handy. It has the same behavior as the compose() but it runs from the left to right

const pipe = (...fns) =>
fns.reduceRight((f, g) => (...args) => f(g(...args)))

Now we can compose in a more natural order, being the order of the parameters the order the functions will run:

const calculate = pipe(sumTen, subtractFive)

If you take these basic functional programming concepts as rules and also take care to write decoupled small functions that only serve a single purpose, you can really start to reuse these pieces of code by combining them in multiple ways and then achieving a great level of flexibility.

Once you are set on an interface for your composable functions, it’s very ease to put something in between, on a composition, to cover the more specific edge case.

In the next article we'll start applying these concepts to real world applications. See you then.


Tag cloud