DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,274 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Jethro Larson
Jethro Larson

Posted on • Updated on

Intro to Functional Combinators in Javascript

Functional programming is at its core about using functions as the primary means of building programs. This means that a constructed functional program is a function. To those accustomed to OOP it might seem absurd to only use functions but with the power and simplicity of function composition and a few additional concepts you can create testable applications of any size. As a quick intro I want to focus on a couple concepts that can help build complex programs from smaller ones.

Compose

You may remember this one from High School math class as "compose" or just "Β°". Given two functions f and g (gβ€‰βˆ˜β€‰f )(x) = g(f(x)). We can implement this in JS as

const compose = (g, f) => (x) => g(f(x))
Enter fullscreen mode Exit fullscreen mode

This is a higher-order function, that is, a function that either takes a function as an argument or returns one. Compose does both, taking two functions and then returning one that applies its argument to the second function and then applies the result to the first one. Let's create a couple example functions to illustrate:

const inc = n => n + 1;
const half = n => n / 2;

const operate = compose(half, inc);

console.log(operate(3)) //=> 2
Enter fullscreen mode Exit fullscreen mode

There's an important constraint to consider; the compose function only works on functions that take one argument (unary functions). Doubly troublesome is that compose itself takes two arguments so it can't be used in a fractal manner. That wont do. Fortunatly there's a cool trick to make any function unary: currying.

Currying

Currying is the act of converting a function that takes multiple arguments into a function that takes the first argument and returns a function that takes the next recursively until all arguments have been passed before returning the result. As an example let's refactor the inc function from above to be based on an add function:

const add = (n, m) => n + m;
Enter fullscreen mode Exit fullscreen mode

The above add function is a normal two-argument(binary) function but we can jam an arrow between n and m to curry it:

const add = n => m => n + m;
// now that its curried we can partially apply it to create 
// our inc function from before
const inc = add(1);
Enter fullscreen mode Exit fullscreen mode

Compose revisited

Now that we know how to curry let's curry the compose function itself. This is also known as the B-combinator so let's call it that here:

const B = g => f => x => g(f(x));

// usage is similar to before
const operate = B(half)(inc)
Enter fullscreen mode Exit fullscreen mode

You may find it hard to think about what a partially-applied compose function is. I like to think of it as a program that has an adapter on it perfect to fit another program. B(half) is a function that will take a program and return one that divides the result by two.

One of the great places to use this is anywhere you see nested function calls:

const process = (arr) =>
  arr.map(a => 
    getUser(getFirstName(a))
  );

// can be rewritten as
const process = (arr) =>
  arr.map(B(getUser)(getFirstName));
Enter fullscreen mode Exit fullscreen mode

This is just the tip of the iceberg and I invite you to try writing these yourself and playing around with them.

Top comments (8)

Collapse
 
jwp profile image
John Peters • Edited on

Thanks Jethro, really nice article, something for me to study in coming weeks for sure.

I was an OOPer for over 15 years. The main take away was the SRP principal (Single Responsibility Principal). Once I learned to assess my brand new block of code and follow that rule, it lead me to the next step. If there were any mixed responsibilities, I immediately refactored them to a new method. This ultimately allowed me to see the end result which was lots of small re-usable (functions) methods. OOP leads us to functional programming styles when we take the SRP principal seriously.

Its not really that different than the DRY principal. Reason? Because if you never repeat your code, you are forced to the functional and composition styles.

Collapse
 
jethrolarson profile image
Jethro Larson Author

I agree. But I've also heard the assertion "functions in the small, objects in the large", so I wanted to emphasize that it is possible and potentially a good idea to use functions all the way up

Collapse
 
jwp profile image
John Peters • Edited on

Yes no problem with that idea. As we build reusable parts, we have no choice but to use Composition as our style, which you referred to in this article. Nice...

If we use only composed functions it is no different than using composed classes. OOPers have been composing classes for 30 years now. The concepts are identical. So really. It's a matter of containment and how to implement.

AFAIK, Typescript compiles everything down to functions anyway.

Thread Thread
 
jethrolarson profile image
Jethro Larson Author

Depending on your compile target, yeah. However, those constructor functions create objects and v8 turns objects into hidden classes so everything is everything :)

blog.sessionstack.com/how-javascri...
See also wiki.c2.com/?ClosuresAndObjectsAre...

Thread Thread
 
jwp profile image
John Peters • Edited on

I wasn't referring to V8 internals, just comparing the new Javascript Class to Functions described here:

developer.mozilla.org/en-US/docs/W...

Collapse
 
hassan_dev91 profile image
Hassan

Thanks.
I found a resource for common combinators in javascript.
gist.github.com/Avaq/1f0636ec5c8d6...

Collapse
 
vuong profile image
Vuong

I just "review" code, not sure about the whole article because I'm still learning JS :P

conat inc = add(1);

should be

const inc = add(1);

Collapse
 
jethrolarson profile image
Jethro Larson Author

Thanks!

You can see total article reactions, views, and listing information by heading over to your dashboard.