---
title: "Extending pure utility functions of Ramda.js"
description:
  "Why and how we built custom pure JavaScript utility functions to extend
  Ramda.js"
canonical_url: "https://www.bigbinary.com/blog/extending-pure-utility-functions-of-ramda"
markdown_url: "https://www.bigbinary.com/blog/extending-pure-utility-functions-of-ramda.md"
---

# Extending pure utility functions of Ramda.js

Why and how we built custom pure JavaScript utility functions to extend Ramda.js

- Author: Neenu Chacko
- Published: May 30, 2023
- Categories: JavaScript

### Introduction

At BigBinary, we are always looking to improve our code.
[Ramda](https://ramdajs.com/docs/)'s focus on functional-style programming with
immutable and side-effect-free functions aligns with this goal, making it our
preferred choice.

While working on [neeto](https://www.neeto.com/), we found the need for specific
functions that could be applied across a range of products but were not already
included in Ramda. We extended Ramda's functions to meet this need and created
our own pure utility functions.

In this blog, we'll explore our motivation for creating these functions and the
benefits they provide, showcasing how they can be generally applicable to a wide
range of products.

### The matches function: the core of our pure utility functions

During the development of Neeto, we encountered instances where long conditional
chains were used to search for objects with deeply nested properties.

For example, consider this `userOrder` object:

```javascript
const userOrder = {
  id: 12356,
  user: {
    id: 2345,
    name: "John Smith",
    role: "customer",
    type: "standard",
    email: "john@example.com",
  },
  amount: 25000,
  type: "prepaid",
  status: "dispatched",
  shipTo: {
    name: "Bob Brown",
    address: "456 Oak Lane",
    city: "Pretendville",
    state: "Oregon",
    zip: "98999",
  },
};
```

We can check if this order is deliverable like this.

```javascript
const isDeliverable =
  userOrder.type === "prepaid" &&
  useOrder.user.role === "customer" &&
  userOrder.status === "dispatched";
```

This approach works but it can be simplified.

Our goal was to simplify the process by focusing on **comparing all the keys of
the pattern to the corresponding keys in the data**. If the pattern matches with
the object, the function should return true. With that in mind, we developed a
function named `matches` to determine if a given object matches a specified
pattern.

With `matches` we should be able to rewrite `isDeliverable` as:

```javascript
const isDeliverable = matches(DELIVERABLE_ORDER_PATTERN, userOrder);
```

Here the `DELIVERABLE_ORDER_PATTERN` is defined as:

```javascript
const DELIVERABLE_ORDER_PATTERN = {
  type: "prepaid",
  status: "dispatched",
  user: { role: "customer" },
};
```

This is how we implemented the `matches` function:

```javascript
const matches = (pattern, object) => {
  if (object === pattern) return true;

  if (isNil(pattern) || isNil(object)) return false;

  if (typeof pattern !== "object") return false;

  return Object.entries(pattern).every(([key, value]) =>
    matches(value, object[key])
  );
};
```

Here, we noticed a limitation in this implementation of the `matches` function.
**It compared the keys and values in the data and the pattern only for strict
equality**. We were not able to use the `matches` function for a situation like
the one mentioned below.

To check if the `userOrder` is being shipped to the city of `Michigan` or
`Oregon`, we were not able to call `matches` function on the key `state`.
Instead, we had to use the following approach along with other conditions.

```javascript
const isToBeShippedToMichiganOrOregon =
  ["Michigan", "Oregon"].includes(userOrder.shipTo.state) &&
  // other long chain of conditions
```

To cover that, we decided to **allow functions as key values in the pattern
object**. With this change, we should be able to write the same as the
following.

```javascript
matches(
  {
    shipTo: { state: state => ["Michigan", "Oregon"].includes(state) },
    // ...other properties
  },
  userOrder
);
```

Here is the modification we have made to the `matches` function to accomplish
this feature:

```javascript
const matches = (pattern, object) => {
  if (object === pattern) return true;

  if (typeof pattern === "function" && pattern(object)) return true;

  if (isNil(pattern) || isNil(object)) return false;

  if (typeof pattern !== "object") return false;

  return Object.entries(pattern).every(([key, value]) =>
    matches(value, object[key])
  );
};
```

As a result of these improvements, the `matches` function can now handle a wider
range of patterns.

```javascript
const user = {
  firstName: "Oliver",
  address: { city: "Miami", phoneNumber: "389791382" },
  cars: [{ brand: "Ford" }, { brand: "Honda" }],
};

matches({ cars: includes({ brand: "Ford" }) }, user); //true
matches({ firstName: startsWith("O") }, user); // true
```

Here, both [includes](https://ramdajs.com/docs/#includes) and
[startsWith](https://ramdajs.com/docs/#startsWith) are methods from Ramda and
they are both curried functions. We will be talking about `currying` of
functions in the upcoming section.

## Neeto's pure utility functions for array operations

With the help of the `matches` function, it became easier for us to work on our
next task at hand: building utility functions that simplify array operations.

### \*By functions

Let's say we have an array of users:

```javascript
const users = [
  {
    id: 1,
    name: "Sam",
    age: 20,
    address: {
      street: "First street",
      pin: 123456,
      contact: {
        phone: "123-456-7890",
        email: "sam@example.com",
      },
    },
  },
  {
    id: 2,
    name: "Oliver",
    age: 40,
    address: {
      street: "Second street",
      pin: 654321,
      contact: {
        phone: "987-654-3210",
        email: "oliver@example.com",
      },
    },
  },
];
```

If we need to retrieve the details of user with the name `Sam`, we will do it
like this in plain vanilla JS:

```javascript
const sam = users.find(user => user.name === "Sam");
```

Since we already have the `matches` function, we could easily create a utility
function that would return the same result as above while removing extra code.

That's how we came up with the `findBy` function that can be used to find the
first item that matches the given pattern from an array.

We defined `findBy` like this:

```javascript
const findBy = (pattern, array) => array.find(item => matches(pattern, item));
```

Now we were able to rewrite our previous array operation as:

```javascript
const sam = findBy({ name: "Sam" }, users);
```

It was also now possible for us to write nested conditions like these:

```javascript
findBy({ age: 40, address: { pin: 654321 } }, users);
// returns details of the first user with age 40 whose pin is 654321
findBy({ address: { contact: { email: "sam@example.com" } } }, users);
// returns details of the first user whose contact email is "sam@example.com"
```

We adopted the concept of
[**currying**](https://javascript.info/currying-partials) from Ramda to shorten
our function definitions and their usage.

Currying is a technique in functional programming where a function that takes
multiple arguments is transformed into a sequence of functions, each taking a
single argument. Simply said, currying translates a function from callable as
`f(a, b, c)` into callable as `f(a)(b)(c)`. You can learn more about currying
and Ramda from our free
[Learn RamdaJS book](https://courses.bigbinaryacademy.com/learn-ramdajs/introduction/currying).

We wrapped the definition of `matches` inside the
[curry](https://ramdajs.com/docs/#curry) function from Ramda as shown below:

```javascript
const matches = curry((pattern, object) => {
  //...matches function logic
});
```

With this update, the `findBy` function could be simplified to:

```javascript
const findBy = (pattern, array) => array.find(matches(pattern));
```

We also used `curry` wrapping for `findBy` function for the same reason:

```javascript
const findBy = curry((pattern, array) => array.find(matches(pattern)));
```

Similar to `findBy` we also introduced the following functions to simplify
development:

- **`findIndexBy(pattern, data):`** finds the first index of occurrence of an
  item that matches the pattern from the given array.
- **`filterBy(pattern, data):`** returns the filtered array of items based on
  pattern matching.
- **`findLastBy(pattern, data):`** finds the last item that matches the given
  pattern.
- **`removeBy(pattern, data):`** removes all items that match the given pattern
  from an array of items.
- **`countBy(pattern, data):`** returns the number of items that match the given
  pattern.
- **`replaceBy(pattern, newItem, data):`** replaces all items that match the
  given pattern with the given item.

Here are some example usages of these functions:

```javascript
findIndexBy({ name: "Sam" }, users);
//returns the array index of Sam in "users"

filterBy({ address: { street: "First street" } }, users);
//returns a list of "users" who lives on First street

removeBy({ name: "Sam" }, users); // removes Sam from "users"

countBy({ age: 20 }, users);
// returns the count of "users" who are exactly 20 years old.

findLastBy({ name: includes("e") }, users);
// returns the last user whose name contains the character 'e', from the array.

const newItem = { id: 2, name: "John" };
replaceBy({ name: "Sam" }, newItem, users);
/*
[
  { id: 2, name: "John" },
  { id: 2, name: "Oliver", age: 40,
  //... Oliver's address attributes },
];
*/
```

### \*ById functions

Applications frequently rely on unique IDs for data retrieval. As a result, when
using **`By`** functions, pattern matching for the ID becomes necessary.

```javascript
const defaultUser = findBy({ id: DEFAULT_USER_ID }, users);
```

To shorten this code, we developed a set of utility functions that can be
invoked directly based on the ID. Let us call them `ById` functions. With `ById`
functions, we can rewrite the previous code as:

```javascript
const defaultUser = findById(DEFAULT_USER_ID, users);
```

Here are some of the `ById` functions we use:

- **`findById(id, data):`** finds an object having the given `id` from an array.
- **`replaceById(id, newItem, data):`** returns a new array with the item having
  the given `id` replaced with the given object.
- **`modifyById(id, modifier, data):`** applies a modifier function to the item
  in an array that matches the given `id`. It then returns a new array where the
  return value of the modifier function is placed in the index of the matching
  item.
- **`findIndexById(id, data):`** finds the index of an item from an array of
  items based on the `id` provided.
- **`removeById(id, data):`** returns a new array where the item with the given
  `id` is removed.

Here are a few examples:

```javascript
findById(2, users); // returns the object with id=2 from "users"

const idOfItemToBeReplaced = 2;
const newItem = { id: 3, name: "John" };
replaceById(idOfItemToBeReplaced, newItem, users);
//[ { id: 1, name: "Sam", age:20, ...}, { id: 3, name: "John" }]

const idOfItemToBeModified = 2;
const modifier = item => assoc("name", item.name.toUpperCase(), item);
modifyById(idOfItemToBeModified, modifier, users);
//[{ id: 1, name: "Sam", ... }, { id: 2, name: "OLIVER", ... }]

const idOfItemToBeRemoved = 2;
removeById(idOfItemToBeRemoved, users);
// [{ id: 1, name: "Sam", ... }]
```

[**assoc**](https://courses.bigbinaryacademy.com/learn-ramdajs/association-methods/assoc)
is a function from Ramda that makes a shallow clone of an object, setting or
overriding the specified property with the given value.

## Null-safe alternatives for pure functions

The `By` and `ById` functions proved to be invaluable to us in improving the
code quality. However, when working with data in web applications, it is quite
common to come across scenarios where the data being processed can be
`null/undefined`. The above-mentioned implementations of the `By` and `ById`
functions will fail with an error if the `users` array passed into them is
`null/undefined`.

In such a case, to use the `filterBy` function, we need to adopt a method like
this:

```javascript
users && filterBy({ age: 20 }, users);
```

So we needed a fail-safe alternative that could be used in places where the data
array can be `null/undefined`. This null-safe alternative should avoid execution
& return `users` if `users` is `null/undefined`. It should work the same as
`filterBy` otherwise.

Hence we created a **wrapper function that would check for data nullity and
execute the child function conditionally**. This is how we did it:

```javascript
const nullSafe =
  func =>
  (...args) => {
    const dataArg = args[func.length - 1];

    return isNil(dataArg) ? dataArg : func(...args);
  };
```

With the help of this `nullSafe` function, we created null-safe alternatives for
all our pure functions.

```javascript
const _replaceById = nullSafe(replaceById);
const _modifyById = nullSafe(modifyById);
```

But with the `nullSafe` wrapping, currying ceased to work for these null-safe
alternative functions. To retain currying, we had to rewrite `nullSafe` using
the [curryN](https://ramdajs.com/docs/#curryN) function from Ramda like this:

```javascript
const nullSafe = func =>
  curryN(func.length, (...args) => {
    const dataArg = args[func.length - 1];
    return isNil(dataArg) ? dataArg : func(...args);
  });
```

## Some other useful functions

### keysToCamelCase

Recursively converts the snake-cased object keys to camel case.

```javascript
const snakeToCamelCase = string =>
  string.replace(/(_\w)/g, letter => letter[1].toUpperCase());

const keysToCamelCase = obj =>
  Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [
      snakeToCamelCase(key),
      typeof value === "object" && value !== null && !Array.isArray(value)
        ? keysToCamelCase(value)
        : value,
    ])
  );

keysToCamelCase({
  first_name: "Oliver",
  last_name: "Smith",
  address: { city: "Miami", phone_number: "389791382" },
});
/*
{ address: {city: 'Miami', phoneNumber: '389791382'},
  firstName: "Oliver", lastName: "Smith",
}
*/
```

### isNot

Returns `true` if the given values (or references) are not equal. `false`
otherwise.

```javascript
const isNot = curry((x, y) => x !== y);
```

Say, you have a task at hand - finding details about users, but specifically
excluding the user named "Sam". In such a scenario, you can retrieve the
information as shown below:

```javascript
filterBy({ name: name => name != "Sam" }, users);
```

But this could be made more readable and concise if we have a function that
finds the non-identical matches from `users` list. For this, you can use the
`isNot` function.

```javascript
filterBy({ name: isNot("Sam") }, users);
// returns an array of all users except "Sam"
```

## Links

- [Human page](https://www.bigbinary.com/blog/extending-pure-utility-functions-of-ramda)
