Handling objects with Ramda

szib

Ivan Szebenszki

Posted on November 10, 2019

Handling objects with Ramda

I recently started learning functional programming and found this awesome library: Ramda. In this post I will show how easy handling objects is with Ramda.

What is Ramda anyway?

Ramda is a functional programming library that emphasises pure function style, immutability and side-effect free functions. It is build of a bunch of small functions that can work effective together.

All these functions are automatically curried, it other words they can called with less argument than they expect. In that case they return a function that holds the argument already passed and expects the rest.

prop, propOr

Let's say we want to log the maths grade of each student or log 'No grade' if they don't have maths grade.

import * as R from 'ramda';

const student1 = {
  name: 'Alice',
  grades: {
    english: 'B',
    history: 'C',
    biology: 'D',
  },
};

const student2 = {
  name: 'Bob',
  grades: {
    maths: 'A',
    english: 'B',
    history: 'C',
  },
};
const student3 = {
  name: 'Cecile',
};

const students = [student1, student2, student3];
students.forEach(student => {
  const grade =
    student.grades && student.grades.maths ? student.grades.maths : 'No grade';
  console.log(`${student.name} \t ${grade}`);
});

// Alice    No grade
// Bob      A
// Cecile   No grade
Enter fullscreen mode Exit fullscreen mode

I can't say it is easy to read and if we had more deeply nested objects the code would become very ugly very quickly. With Ramda the same code would look like this.

import * as R from 'ramda';

const gimmeTheGrades = R.prop('grades'); 

students.forEach(student => {
  const grade = R.propOr('No grade', 'maths', gimmeTheGrades(student));
  console.log(`${student.name} \t ${grade}`);
});

// Alice    No grade
// Bob      A
// Cecile   No grade
Enter fullscreen mode Exit fullscreen mode

gimmeTheGrades is a function that returns the grades property of the object I pass to it, if no grades then it simply returns undefined. propOr takes an extra argument - default value. If the result is falsey, it returns the default value.

If I needed a grade with default value somewhere else later in my app, I would have done this.

import * as R from 'ramda';

const gimmeTheGrades = R.prop('grades');
const gradeWithDefault = R.propOr('No grade');

students.forEach(student => {
  const grade = gradeWithDefault('maths', gimmeTheGrades(student));
  console.log(`${student.name} \t ${grade}`);
});

// Alice    No grade
// Bob      A
// Cecile   No grade
Enter fullscreen mode Exit fullscreen mode

path, pathOr

What if we need a value of a deeply nested property? We can use path or pathOr. It works the same as prop and propOr, but takes an array of stings, instead of one single string.

import * as R from 'ramda';

const gradeWithDefault = R.pathOr('No grade');
const gimmeTheMathGrade = gradeWithDefault(['grades', 'maths']);

students.forEach(student => {
  const grade = gimmeTheMathGrade(student);
  console.log(`${student.name} \t ${grade}`);
});

// Alice    No grade
// Bob      A
// Cecile   No grade
Enter fullscreen mode Exit fullscreen mode

getter/setter

It is very easy to define getter and setter functions for a property with lens function. The first argument a function that gets the property, the second is the one that sets it. The setter should not change the data structure.

import * as R from 'ramda';

const gradeWithDefault = R.pathOr('No grade');
const mathsLens = R.lens(
  gradeWithDefault(['grades', 'maths']),
  R.assocPath(['grades', 'maths']),
);

console.log(R.view(mathsLens, student1)); // No grade
console.log(R.view(mathsLens, student2)); // A

const newStudent1 = R.set(mathsLens, 'F', student1);
const newStudent2 = R.set(mathsLens, undefined, student2);

console.log(R.view(mathsLens, newStudent1)); // F
console.log(R.view(mathsLens, newStudent2)); // No grade

console.log(newStudent2);
// {
//   name: 'Bob',
//   grades: { maths: undefined, english: 'B', history: 'C' }
// }
Enter fullscreen mode Exit fullscreen mode

Note: new Student has grades.maths property, but it is undefined.

objOf, mergeLeft

objOf creates an object with a single key:value pair, which can be merged with another object, so if we want to create an array with student objects we can do it like below.

import * as R from 'ramda';

const names = ['Alice', 'Bob', 'Cecile'];
const defaultStudent = {
  grades: {
    english: null,
    history: null,
    biology: null,
  },
};

const createSudents = R.pipe(
  R.map(R.objOf('name')),
  R.map(R.mergeLeft(defaultStudent)),
);

const students = createSudents(names);

console.log(students);
Enter fullscreen mode Exit fullscreen mode

pipe takes functions as arguments and call them in order, passing the result of each function to the next. First we map through the names and create objects with a name property.

[ { name: 'Alice' }, { name: 'Bob' }, { name: 'Cecile' } ]
Enter fullscreen mode Exit fullscreen mode

Then we feed this to the next map and merge each one with defaultGrades.

[
  {
    name: 'Alice',
    grades: { english: null, history: null, biology: null }
  },
  {
    name: 'Bob',
    grades: { english: null, history: null, biology: null }
  },
  {
    name: 'Cecile',
    grades: { english: null, history: null, biology: null }
  }
]
Enter fullscreen mode Exit fullscreen mode

Thanks for reading it. Happy coding. ❤

Say hello.
LinkedIn | Github | Instagram

💖 💪 🙅 🚩
szib
Ivan Szebenszki

Posted on November 10, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Working on the "Ramda Ramp-Up Guide"
javascript Working on the "Ramda Ramp-Up Guide"

November 12, 2020

RamdaJS: transduce
javascript RamdaJS: transduce

April 3, 2020

RamdaJS: Using it for the first time
javascript RamdaJS: Using it for the first time

March 19, 2020

Handling objects with Ramda
javascript Handling objects with Ramda

November 10, 2019