Ramda your JavaScript

Nowadays, there is always something exciting to discover in the world of the developer: new frameworks, libraries, code styles, inspiring designs, methodologies, and a lot more. For me, in recent months, I’ve been most interested in functional programming in JavaScript.

The functional programming paradigm is entirely new to me, but it has made me think less traditionally when it comes to writing code. I believe that writing [and thinking] in a functional way will make your code more readable; you will eventually have a more consistent language across developers in your team. It will be easier to debug and refactor your code later on. You will probably also write less code and make more useful stuff. Doing functional programming is different but also a lot of fun when you get used to it.

I am a consultant at Computas AS. I’ve been working as a front-end developer for three years, with the main focus on the ReactJs library and static website built with GatsbyJs. Currently, I’m on a project where my team is using a functional JavaScript library called RamdaJs. After we started using RamdaJs, we’ve seen that the structure of our codebase is cleaner, has increased readability and a more uniformed language among the developers.

In this post, I want to show you some cool things that RamdaJs — which I will now call Ramda for simplicity and laziness — can do. First of all, it is worth knowing that Ramda is a JavaScript library that helps you write functional, concise and reusable code. Ramda is declarative (rather than imperative which is the most common method these days). So to get a better understanding of why Ramda is useful, we should have a basic knowledge of functional programming principles and then some examples with Ramda.

We are going into the following topics:

  • Currying
  • Pure functions
  • Composition
  • Immutability
  • Example: The List of Notes

Currying

Currying is a technique where we can “think of transforming” a function that takes many arguments to a more specific function that takes fewer arguments. With the help of currying, we can split a big and nested function into smaller functions. Then we remove complexity and make a function concise and reusable.

Curried functions

A curried function won’t do anything before it has received all the required arguments that it needs. It will take each argument one at a time.

Given that we have a function that takes three parameters, a curried version of the function will take one argument and return a function that takes the next argument, which then returns a function that takes the third argument.

A function with three parameters that sums the numbers provided for each function may we written as:

// add = a => b => c => Number
const add = a => b => c => a + b + c;

To make that function fully work we need to apply all of its parameters. We can do this in several ways and all the examples below are allowed:

add(1, 2, 3) // 6
add(4)(1, 10) // 15
add(2, 3)(2) // 7
add(3)(2)(1) // 6

What makes Ramda great is that their functions are automatically curried, which allows us to easily build up sequences of small and simple functions or old functions that we already have created.

Pure functions

A pure function is an essential concept in functional programming. By pure, we mean that a function should be without so-called “side effects.”

Some basic rules for pure functions are:

  • It does not assign new values to outstanding variables, i.e. outside its own scope, between { and }.
  • It does not read or write to a database or do API calls.
  • It does not mutate incoming arguments. (This is a very strict rule, and it’s not always possible to prevent using side effects in our functions. But for the most part, all functions should be as clean as possible.)

Sometimes, you may even want to nest functions within other functions that use variables from a parent function scope. In this case, you should consider splitting the function into smaller ones, and instead pass values through them.

Composition

Composition is when we take a return function and pass it as the parameter to another function. Take a look at the code below:

const flip = x => x.split('').reverse().join('')
const exclaim = x => `${x}!`
const flipAndShout = x => exclaim(flip(x))
flipAndShout('dlroW olleH') // "Hello, World!"

Here we call two functions where both do something to the value x. This code is hard to read. It would be even harder if we added many more functions to the flipAndShout variable.

The next example is a compose function:

const compose = (f, g) => f(g(x))

Arguments f and g are functions and x is the value that runs through them. Here the function g will run first, then the function f which will make a call flow from right to left. To get an idea of how this can be implemented, we can refactor the code from the previous example and use our compose function instead:

const flip = x => x.split('').reverse().join('')
const exclaim = x => `${x}!`
const flipAndShout = compose(exclaim, flip)
flipAndShout('dlroW olleH') // "Hello, World!"

This is much easier to read than a bunch of function calls. We can just by taking a quick look see what the flipAndShout function will return. Later we will look at some examples of Ramda’s composition functions.

Immutability

The concept of immutability and immutable data is also important in functional programming. Immutability means that existing data shall not be changed. When we initialize a variable with some value (string, array, number), it should not change data type (or be cast as) nor overwritten — not even object-properties or elements in an array.

If existing data isbeing changed, we may end up having some big problems when doing debugging, and the code may not be as readable if someone, at some point, needs to refactor it.

May we do some Ramda now…?

Yep. Ramda is a great library for getting started on thinking functionally with JavaScript. Ramda provides a great toolbox with a lot of useful functionality and decent documentation. If you’d like to try the functionality as we go, Ramda has a REPL you may check out.

Installation

Ramda can be installed in several ways, via Yarn or NPM (but they also offer some CDN’s on their website):

yarn add ramda
// or
npm install ramda

Usage

Require/import it into your project:

const R = require('ramda')
// or
import R from 'ramda'

Or you can import each function if that suits you better. You then don’t need to prepend the R. for each function used:

import { map, filter, prop } from 'ramda'

If you are using the REPL there’s no need to import anything. You can just write the functions (or copy/paste directly from here) like they are already available and ready out of the box.

Example: The List of Notes

A common thing that usually all developers are going to create once in time is list of notes and reminders. We add notes/reminders to our list, and when we are finished with one we archive it.

For such a list of notes, we’d like a data set that contains both regular notes and reminders. We’ll be using the following notesList array for our example. Objects with a valid (not null) “dueDate”-property are reminders while those without are regular notes:

const notesList = [
  {
    title: "Buy milk and bread",
    createdAt: '2020-04-04',
    dueDate: null,
    archived: false,
  },
  {
    title: "Pick up a package at the post office",
    createdAt: '2020-04-04',
    dueDate: null,
    archived: false,
  },
  {
    title: "Take a walk with Yoda",
    createdAt: '2020-04-04',
    dueDate: null,
    archived: true,
  },
  {
    title: "Read 15 minutes",
    createdAt: '2020-03-07',
    dueDate: '2020-03-08',
    archived: false,
  },
  {
    title: "Do 30 minutes workout",
    createdAt: '2020-03-01',
    dueDate: '2020-03-04',
    archived: false,
  },
]

Filtering

A typical action to do with this list is to filter out what we’ve already completed (in this case, “archived”). By using the build-in Array-methods, we can achieve what we want:

const filterNotes = notesList.filter(note => !note.archived);

With Ramda we can do the following:

const filterNotes = R.filter(R.propEq('archived', false));

Hmm… what happened here? We are not passing any parameters to the filterNotes function that uses the Ramda syntax. This is because the function is of pointfree-style.

Pointfree-style is also a functional programming style where the function definition (the body of the function) does not reference the functions arguments. We can get a pointfree-style by calling a function that returns a function, such as a curried function. Let us remind ourselves of the curried example earlier in this post:

// add = a => b => c => Number
const add = a => b => c => a + b + c;

Therefore we do not need to assign arguments to the function definition. Or said otherwise, we do not need to “tell” the Ramda function what data it needs.

We still need to call the filterNotes function with an argument to make it work, and the argument must be a list of objects and the objects must contain at least an archived property.

Sorting by property

Now we have filtered out all the notes that are not archived. Let’s now sort the notes by their creation date. For this we can use the sortBy and prop functions from Ramda:

const filterNotes = filter(propEq('archived', false));
const sortByCreatedAt = sortBy(prop(‘createdAt’));
const filteredNotes = filterNotes(notesList);
sortByCreated(filteredNotes);

Function composition

The code above is not very readable, but how can we make it so? Earlier, we talked about composition. With function composition, we can build up pipelines of functions of other small functions. Ramda provides a couple of functions for doing function composition and these are called pipe and compose.

Both functions take one or more arguments and return a function. pipe does left-to-right composition. It takes the first argument, passes the result to the next argument and continues like this until the last argument is finished doing what it does. compose is a little different and applies arguments right-to-left.

It doesn’t matter which of them you use, but go for what reads you best. I prefer to use compose in the examples as this is what my eyes handle best.

We can use compose on filterNotes and sortByCreatedAt:

const filterNotes = filter(propEq('archived', false));
const sortByCreatedAt = sortBy(prop('createdAt'));
const sortNotes = compose(sortByCreatedAt, filterNotes);
sortNotes(notesList);

But what if we for some reason want to sort the notes in a descending order and at the same time reuse code that we have already written? Ramda’s reverse function will come in handy here. reverse simply takes a list and returns it in reversed order:

const filterNotes = filter(propEq('archived', false));
const sortByCreatedAt = sortBy(prop('createdAt'));
const sortByCreatedAtDescending = compose(reverse, sortByCreated);
const sortNotes = compose(sortByCreatedAtDescending, filteredNotes);
sortNotes(notesList);

Group by category

Ramda has a function called groupBy which splits a list into sub-lists and then stores the result in an object. For our example we can pass a function that does a conditional statement for the “dueDate” property in each object and then returns what key (note or reminder) a note should be grouped within.

Our function can be written like this:

const groupByType = groupBy(n => n.duedate ? 'reminders' : 'notes')

And implement it with the rest of the code:

const filterNotes = filter(propEq('archived', false));
const sortByCreatedAt = sortBy(prop('createdAt'));
const groupByType = groupBy(n => n.dueDate ? 'reminders' : 'notes')
const filterByType = compose(groupByType, sortByCreatedAt);
const sortedNotes = compose(
      filterByType,
      filterNotes
    );
sortedNotes(notesList)

This is great. Now we have all the notes and reminders grouped (each type as object keys) and we get this:

{
    notes: [
        {
            archived: false,
            createdAt: "2020-04-04",
            dueDate: null,
            title: "Buy milk and bread"
        },
        {
            archived: false,
            createdAt: "2020-04-04",
            dueDate: null,
            title: "Pick up a package at the post office"
        }
    ],
    reminders: [
        {
            archived: false,
            createdAt: "2020-03-01",
            dueDate: "2020-03-04",
            title: "Do 30 minutes workout"
        },
        {
            archived: false,
            createdAt: "2020-03-07",
            dueDate: "2020-03-08",
            title: "Read 15 minutes"
        }
    ]
}

See result in Ramda REPL

Finishing up

Now, some of the object properties in our notesList may not always be that necessary.

We could have a user interface where we only need some of the properties to display them visually (and the rest is left unused). These properties we can consider as the important information. project is a Ramda function that is similar to SQL select and can select the (important) properties that we want from each object:

const importantFields = project(['title', 'createdAt', 'dueDate']);

Let’s implement this with the rest of the code:

const filterNotes = filter(propEq('archived', false));
const sortByCreatedAt = sortBy(prop('createdAt'));
const sortByCreatedAtDescending = compose(reverse, sortByCreatedAt);
const groupByType = groupBy(n => n.dueDate ? 'reminders' : 'notes')
const importantFields = project(['title', 'createdAt', 'dueDate']);
const filterByType = compose(groupByType, sortByCreatedAt)
const sortedNotes = compose(
      map(importantFields),
      filterByType,
      filterNotes
    )
sortedNotes(notesList)

See final result in REPL

Now we have an object containing two arrays, sorted by created date and active status, how great isn’t that? As our final result, see how clean it is and we got just the data that we need:

{
    notes: [
        {
            createdAt: "2020-04-04",
            dueDate: null,
            title: "Buy milk and bread"
        },
        {
            createdAt: "2020-04-04",
            dueDate: null,
            title: "Pick up package at mail office"
        }
    ],
    reminders: [
        {
            createdAt: "2020-03-01",
            dueDate: "2020-03-04",
            title: "Do 30 minutes workout"
        },
        {
            createdAt: "2020-03-07",
            dueDate: "2020-03-08",
            title: "Read 15 minutes"
        }
    ]
}

Thanks for reading!


Ramda your JavaScript was originally published in Grensesnittet on Medium, where people are continuing the conversation by highlighting and responding to this story.