After Sliced Bread Came Ramda.js (Part II)

alt A ram

The Cambrian Explosion (Ramda Arrives)

This is Part II of a two part series on functional programming here's Part I

Screaming Goat
Ramda.js for the win

To be fair, ramda is not attempting to break a lot of new ground in FP (functional programming). They are trying to provide a good functional utility belt for the JS community.

Then why am I being so dramatic about ramda's arrival on the scene?

Think of it like this. The board is set. All the pieces are in place for me to learn functional programming.

I already understand a lot of the basic concepts and then ramda comes along. It was the right timing in my journey, to crack FP wide open for me once and for all.

reduce

This little bugger is a work horse. It's Turing Complete, of course what isn't these days...

You can get rid of all your dirty little forEach loops now. You could even use reduce to replace map and filter or any list transformation.

First let's go with an example of reduce that's not sum:

const values = object => Object.keys(object).reduce(  
  (array, key) => array.concat(object[key]),
  []
);

const object = { hello: 1, there: 2, world: 3 };

console.log(values(object)); // [ 1, 2, 3 ]  

Think about the power of reduce. You can take an array and transform it into anything. Another array, a number, a string, an object.

You may say, "Well, what if I don't have an array, what if all I have is a measly number or an object?"

There's nothing stopping you from wrapping that value in an array. Then you get all the goodness of map, filter, reduce, etc. And when you're done, you can always unbox it from the array.

So reduce can actually transform anything into anything. Cool.

curry

Curry is confusing to everyone at first. But the truth is, it's simple.

Way back when there was this guy named John McCarthy. He kind of looked like Colonel Sanders. And he wrote Lisp, based on the lambda calculus and...it's no wonder no one understands currying

Okay starting over.

Here is currying in a nutshell:

const regularOldAdd = (x, y) => x + y;  
regularOldAdd(3, 4); // 7

const curriedAdd = x => y => x + y;  
curriedAdd(3)(4); // 7  
curriedAdd(3, 4); // [Function]  
curriedAdd(3); // [Function]  

Okay so what is the deal with that? The first function is simple enough. We get that.

The second one is weird. We have a double arrow function, so let's think about what that means.

We return a function that takes an argument x. Then this returns another function that takes argument y. And this second function is what returns the result.

This is all currying is at the core. Having all your functions take one argument only.

I know what you're thinking, this is stupid right? Wrong.

But it is obnoxious that we can't call curriedAdd(3, 4) without getting a function back. Let's address that first, then we'll explore the usefulness of curry.

const {curry} = require('ramda');

const add = (x, y) => x + y;  
const curried = curry(add);

curried(3, 4); // 7  
curried(3)(4); // 7  

Okay good. Ramda uses some Javascript trickery to let you call the function either way.

But why is it good? Remember earlier when I showed you that you could configure a function on the first pass?

function getIncrementor() {  
  var ctr = 0;
  return function incrementor() {
    ctr++;
    return ctr;
  };
}

We configure the ctr variable and get another function that increments the ctr.
const inc = getIncrementor(); inc(); inc();

If you think about it, curry is a more convenient way to do that:

const {curry} = require('ramda');

const todos = [  
  { done: false, text: 'Iron the shirts' },
  { done: false, text: 'Make the bed' },
  { done: false, text: 'Make the sandwiches' },
  { done: true, text: 'Procrastinate' },
];

const getItems = curry((done, letter, array) => {  
  return array
    .filter(x => x.done === done)
    .filter(x => letter ? x.text.startsWith(letter) : true);
}); // This is a curried function

const getUnfinishedItems = getItems(false, ''); // This is a function, not the unfinished items

const getUnfinishedItemsStartingWith = getItems(false);

const getFinishedItems = getItems(true, '');

getUnfinishedItems(todos);  
/*
[ { done: false, text: 'Iron the shirts' },
  { done: false, text: 'Make the bed' },
  { done: false, text: 'Make the sandwiches' } ]
*/

getUnfinishedItemsStartingWith('M', todos);  
/*
[ { done: false, text: 'Make the bed' },
  { done: false, text: 'Make the sandwiches' } ]
*/

getFinishedItems(todos); // [ { done: true, text: 'Procrastinate' } ]  

Notice that from our curried function, we are able to derive many other useful functions.

And because we are currying, we don't have to do this:

const getUnfinishedItems = (done, letter, array) => getItems(false, '', array);

// Instead we do this
const getUnfinishedItems = getItems(false, '');  

The latter is much shorter and much more flexible, and cost us nothing. All we had to do was wrap the getItems function in a call to curry.

compose/pipe

Do you like Linux? If so then this section is for you.

ls  
LICENSE      README.md    node_modules package.json     src   yarn.lock

ls | grep src  
src  

Unix pipe. All good right? Okay then how about:

const {filter, inc, map, pipe} = require('ramda');

const array = [1, 2, 3, 4];  
const getEvensAndIncrement = pipe(  
  filter(x => x % 2 === 0),
  map(inc)
);

getEvensAndIncrement(array); // [ 3, 5 ]  

Pipe is straightforward. Whatever you pass into it, gets passed into the first function in your list. And then the result just makes it's way down the functions. The same way pipe works on the command line.

Oh and compose? It's the same as pipe, with one difference. Instead of starting at the top of your list of functions, it starts at the bottom and works its way up.

Immutability

At some point in your research on functional programming, immutability will come up. If something is immutable, then you can't change it or you get an error.

In ES6, strings are immutable:

const x = 'hello';  
x = 'world';  
TypeError: Assignment to constant variable.  

So are Number and Boolean.

But objects are not completely immutable in ES6:

const x = { name: 'Chris' }; // { name: 'Chris' }  
x = {};  
TypeError: Assignment to constant variable.  
    at repl:1:3
    at sigintHandlersWrap (vm.js:22:35)
    at sigintHandlersWrap (vm.js:96:12)
    at ContextifyScript.Script.runInThisContext (vm.js:21:12)
    at REPLServer.defaultEval (repl.js:346:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:545:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)
x.name = 'Bob'; // { name: 'Bob' }  

Oh how unfortunate that is. I'm sure this is due to some desire for performance, but it limits the utility of the const keyword. I would have much preferred to have const be recursively immutable on Objects and Arrays.

We will see why this would be a good thing.

In ES6, they made the same mistake with arrays:

const y = [1, 2, 3]; // [ 1, 2, 3]

y = [];  
TypeError: Assignment to constant variable.  
    at repl:1:3
    at sigintHandlersWrap (vm.js:22:35)
    at sigintHandlersWrap (vm.js:96:12)
    at ContextifyScript.Script.runInThisContext (vm.js:21:12)
    at REPLServer.defaultEval (repl.js:346:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:545:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)

y.push(4);  
console.log(y); // [ 1, 2, 3, 4 ]

y.shift();  
console.log(y); // [ 2, 3, 4 ]  

So why is this bad? For a couple reasons.

First, a minor reason. Let's say you're using something like Redux/React.

If the whole state tree is immutable, then you can do this: previousState === newState

to check if there are any changes to your state that the Dom needs to update.

This is cheap because on an object or array === does a simple reference check.

The way ES6 implements const, this reference check is useless. We may have the same reference but things inside have changed.

That leaves us with looping through looking for changes. So I guess boo for that.

There is an npm module called deep-freeze that can help.

If you give deep-freeze an object or array, it will loop through and make everything immutable.

There is some cost to this, so it may be advisable to only turn this on during development. Then you can find all the instances where you are mutating Redux.

There is a much larger problem with mutating state. Mutating state can cause massive problems in an application.

The larger the application and the more shared global state, the worse the symptoms will be.

When you store state in an object or an array and you pass that state around, you are passing around a reference.

There is only one copy of the data in memory. You are passing around the address of where that memory is.

If you change something in that state, then it changes everywhere.

You won't see why this is such a big problem until you do.

Think about it. Let's say I have some state like this:

const users = [  
  { name: 'Bob', age: 50 },
  { name: 'John', age: 55 }
];

And it used on various screens. But then a coder decides that they don't need age. So they mutate the array, by setting it to user objects without ages.

Now everything that relies on age breaks. And there will be all sorts of weird bugs and display issues.

It seems like this wouldn't be that terrible to track down. But in a large application, with lots of shared global state, it is a nightmare.

In fact, my recommendation for many applications like this is simple: rewrite it.

And if you're into event-based programming, that can get complicated too. If you have events that fetch data and set it in the state and then those events kick of other events that kick off other events... If all along that path, everyone's mutating state as they go, expect enormous pain.

But if everything is immutable, problem solved. You can't change a reference once it is set. And if you want to change your state then you have to give it a completely new copy of the state.

There's a cost to be sure. But you can turn it off in production if you're concerned about performance. You'll still get the benefits of having some guard rails up during development that can guide you.

Pattern Matching

You don't get this in Javascript yet, but there's a proposal in stage 0.

Pattern matching is a better and more reliable way to write if and else-if statements.

In Haskell:

sign x |  x >  0        =   1  
       |  x == 0        =   0
       |  x <  0        =  -1

Or in Javascript:

const sign = x => {  
  if (x > 0) return 1;
  if (x === 0) return 0;
  if (x < 0) return -1;
};

Okay so there's not much difference here. The Javascript version is a little more wordy and 5 lines instead of 3.

The real difference is in the Haskell compiler. Haskell pattern-matching requires you to handle all possible cases in your pattern matches. In this case, we do, because we handle all positive numbers, all negative numbers and 0.

What if we didn't handle all cases? If we missed handling 0 or something. The Haskell compiler would throw an error. This is great because it forces you to handle a lot of edge cases that you could miss.

In Javascript, you're on your own to make sure you're thinking about the edge cases.

In Ramda there is the cond function which will give you some small satisfaction:

const some = R.cond([  
  [R.gt(0), R.always(1)],
  [R.equals(0), R.always(0)],
  [R.lt(0), R.always(-1)]
]);

But this won't give you the benefits of having a compiler like Haskell. In fact, nothing will...

Point-Free (Tacit) Style

Which brings me to my next trick: point-free style. I bring this up because of the example we looked at above:

const some = R.cond([  
  [R.gt(0), R.always(1)],
  [R.equals(0), R.always(0)],
  [R.lt(0), R.always(-1)]
]);
some(42); // 1  
some(0); // 0  
some(-1000); // -1  

Notice that some is a function. Also notice that we don't have a function signature like const some = x => R.cond(. So what's up with that?

Well, almost everything you use in Ramda already returns a function. cond returns a function that expects a value already. So doing this is redundant:

const some = x => R.cond([ ... ])(x);  

Since cond already gives us a function, the left and right sides cancel each other out. Which is why we have const some = R.cond([ ... ]); instead.

It's the same reason these are equal:

promise.then(data => console.log(data));  
// vs.
promise.then(console.log);  

Point-free, or tacit, style is when your function doesn't reference the data it is working on.

Or some crazy shit like that.

The more you use Ramda, the more you will become familiar with these.

const name = person => person.name;  
// vs.
const name = R.prop('name');  

Point-free style is cool, but you know what's really cool? A billion dolla--

Point-free style is cool. But don't beat yourself up trying to make everything point-free. If you're googling, then it's likely not worth it.

flatMap

First, let's get something out of the way. flatMap is different than flatten.

flatten does this:

const nestedArrays = [[1, [2, 3], 4], [5, 6]];  
R.flatten(nestedArrays); // [1, 2, 3, 4, 5, 6]  

flatMap goes a little further: (By the way Ramda, calls flatMap chain instead)

const scores = [  
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

R.chain(R.head)(scores); // [1, 4, 7]  
R.chain(R.identity)(scores); // [1, 2, 3, 4, 5, 6, 7, 8, 9]  
R.chain(R.tail)(scores); // [2, 3, 5, 6, 8, 9]  

It's pretty simple.

  • map will run a function against each item in an Array and return the new Array with the new results.
  • flatten will take a nested Array and return a flat Array.
  • flatMap will run a function against each item in an Array and then flatten that resulting Array.

Let's look at another example:

const sentences = ['hello there world', 'what time is it', 'flashing lights'];

const getWords = sentence => sentence.split(/\s+/g);  
getWords(sentences[0]); // ["hello", "there", "world"];

const getAllWords = R.chain(getWords);  
const words = getAllWords(sentences);  
// ["hello", "there", "world", "what", "time", "is", "it", "flashing", "lights"]

This is a more practical example. We have a function getWords that will take an item and return a list. If we map over the sentences we will have a nested Array of words like this:

[
  ["hello", "there", "world"], 
  ["what", "time", "is", "it"],
  ["flashing", "lights"]
]

But with chain or flatMap, we end up with what we want, which is all the words in all the sentences.

In the imperative world, we could loop over the sentences. And then do what we want with the resulting words. Or add them to an Array.

But in functional programming, everything is a function. We need to be able to take results such as these and flatten them inline, so we can use these results as part of a pipeline.

The goal is to stay within a functional pipeline, without having to break out of it.

What's Your Type?

In most languages a boolean has a special value of true and false as opposed to something like "true" and "false".

And you can do things like this:

const success = true;  
if (success) {  
...
}

Because you can do that, the compiler has to know how to use true and false in if/else expressions.

The Javascript compiler also needs to know how to convert other types to boolean, which is a whole other can of worms..

But in Elm we could do it like this:

type Boolean = True | False  
success = True  
if success == True then  
...

There's power in the notion that you could define such a simple Boolean type. And you could use this type like any other language provided bool type. But with nothing special built into the compiler, to make it work.

Another revelation I had in my readings. Say we are using Flow in ES6:

type EmailType = string;  
const email: EmailType = 'yolo@foreal.com';  

Hmmm, that's strange. Why would we do that? Does it achieve anything?

You still have a string in the end, so it may seem pointless. But let me tell you why this is useful.

If you never touched this email type then you would still benefit. It allows you to see every reference to this type in your code.

But is it realistic to expect that we will never change the email type? We could add functions that validate the email address. That function could use EmailType in its signature instead of string.

Or if we decided that we wanted EmailType to be an object, with an email address as well as inbox and sent properties. We could limit the length of an email address by passing it into a constructor.

And these refactorings would can succeed. Since we have this placeholder type to find and update all references.

And the Grand Covfefe...

Here is an example of how I would use a functional pipeline today:

import R from 'ramda';

const newEmployees = [  
  {
    name: {
      first: 'Theodore',
      last: 'Dandy',
    },
    age: 20,
    kids: [],
  },
  {
    name: {
      first: 'Simon',
      last: 'Smith',
    },
    age: 30,
    kids: ['Chucky'],
  },
  {
    name: {
      first: 'Alivn',
      last: 'Smith',
    },
    age: 40,
    kids: ['Jekyl', 'Hyde'],
  }
];

const pretty = any => JSON.stringify(any, null, 2);  
const peak = R.tap(R.compose(console.log, pretty));

const lastNameIs = R.pathEq(['name', 'last']);  
const getFullName = x => `${x.name.first} ${x.name.last}`;

const query = R.curry((where, select, sort) =>  
  R.pipe(
    R.filter(where),
    peak,
    R.map(select),
    peak,
    R.sortBy(sort),
    peak,
  )
);

const employeeQuery = query(  
  lastNameIs('Smith'), 
  getFullName, 
  R.identity
);

/* 1st peak (filtered):
[
  {
    "name": {
      "first": "Simon",
      "last": "Smith"
    },
    "age": 30,
    "kids": [
      "Chucky"
    ]
  },
  {
    "name": {
      "first": "Alvin",
      "last": "Smith"
    },
    "age": 40,
    "kids": [
      "Jekyl",
      "Hyde"
    ]
  }
]

2nd peak (mapped):  
[
  "Simon Smith",
  "Alvin Smith"
]

3rd peak (sorted):  
[
  "Alvin Smith",
  "Simon Smith"
]
*/

So let's talk about the functions in this example:

pretty

const pretty = any => JSON.stringify(any, null, 2);

That gives us a pretty JSON representation of whatever we pass it. The null and 2 may not be pretty, but that's the part that formats our string to be pretty.

peak

const peak = R.tap(R.compose(console.log, pretty));

Tap is a handy little function for debugging, logging and development. Tap takes a function argument first. Then it will take anything as it's second argument.

All tap does, is it runs the function with your second argument and then returns the second argument. So tap enables this:
x => console.log(x); return x;

The return x part is important, that's what allows us to continue on in our pipeline.

lastNameIs

const lastNameIs = R.pathEq(['name', 'last']);

This is the same as:
const LastNameIs = R.curry((employee, lastName) => employee.name.last === lastName);

The first one is better. It's much shorter for starters. Ramda curries everything, which is why you have to curry in the second one, to get that same benefit.

pathEq may look confusing, but once you know what it does, it's pretty simple to understand at a glance.

In the first example, there's not much to test. You don't have to test pathEq because Ramda presumably tests that. As long as you have an object with a valid path of name.last then that function will work.

The second example is trivial, but it is actual code that you may have gotten wrong. Depending on your tooling, it could be easy to miss something.

query

const query = R.curry((where, select, sort) =>  
  R.pipe(
    R.filter(where),
    peak,
    R.map(select),
    peak,
    R.sortBy(sort),
    peak,
  )
);

We now have a generic query function that you can apply to any array. We pass the functions into our query function. And nothing in our definition says anything about the employee type. So we could use this function to query other types in an array as well.

employeeQuery

const employeeQuery = query(  
  lastNameIs('Smith'), 
  getFullName, 
  R.identity
);

Nifty. We configure an employee specific query. lastNameIs('Smith') and getFullName that's a thing of beauty, we may as well be writing prose here.

R.identity what the hell? Why is the sort function the identity function?

Well think about what our result looks like at this point in the pipeline:

[
  "Simon Smith",
  "Alvin Smith"
]

Okay so we have strings. For this example it's fine to sort the names alphabetically.

And if all we want to do in the sort function is have the string return itself, we use:

Our new friend, the identity function.

If you found this article to be helpful, then you should subscribe here.

Subscribe to our mailing list

* indicates required
Subscribe
X