Utility libraries like Ramda and lodash/fp make functional programming (FP) very accessible. To use these libraries effectively, an understanding of what's going on under the hood is still required, and a good grasp of FP concepts goes a long way

lodash/fp: an instance of lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods - lodash/fp Guide

Ramda: a library designed specifically for a functional programming style, one that makes it easy to create functional pipelines, one that never mutates user data - Ramda

The FP Penny Drops

Up until two years ago I had programmed in a very imperative object-orientated style. From time to time I had read articles on functional programming, but it seemed like an esoteric topic - not something I could see myself using in practice

It wasn't until I was introduced to Ramda and went through a shallow learning curve (with the x axis representing time😉) that I understood the practicality of FP concepts. I began to see functional composition, higher-order functions, and FP in general, in a new light

In this article I'll detail the concepts and features I learned along the way. What I found especially helpful was was creating vanilla ES6+ versions of key Ramda and lodash/fp functions - this was where it all came together for me - so I'll add in some examples of those at the end

Auto Currying

One of the core features of FP libraries is auto-currying, where only some of a functions' parameters are supplied when it's invoked, with a returned function expecting the remaining ones

Lodash Currying

Given the following collection, we want a reusable function that returns an Array of all of the code property values i.e. ["A", "B"]:

const collection = [
  { id: 1, code: 'A' }, 
  { id: 2, code: 'B' }
];

lodash (no currying)

const getCodes = arr => {
  // no currying, wrapper function required
  return _.map(arr, 'code');
};
getCodes(collection) // ["A", "B"]

lodash (with currying)

const getCodes = _.curryRight(_.map, 2)('code');
getCodes(collection) // ["A", "B"]

lodash/fp (auto-curried)

// fp.map iteratee can be a function or property key
const getCodes = fp.map('code');
getCodes(collection) // ["A", "B"]

Function-First, Data-Last

Auto-currying ties in closely with another fundamental aspect of FP libraries: function-first, data-last

Info: in functional programming parlance the function here is referred to as an iteratee, and the data as a functor (anything that can be mapped over)

Lodash Param Order

Given the following collection, we want a reusable function that returns an Array of all of the values incremented by 10 i.e. [10, 11, 12]:

const collection = [0, 1, 2];

lodash (data-first)

const add10 = value => _.add(value, 10);
// _.map is data-first
const incrementData = arr => _.map(arr, add10);
incrementData(collection) // [10, 11, 12]

lodash/fp (function-first)

// fp.map is auto-curried, function-first, data-last
const incrementData = fp.map(fp.add(10));
incrementData(collection) // [10, 11, 12]

Immutable

All Ramda and lodash/fp functions are immutable - they never mutate/change the data passed in. New arrays and objects are returned

lodash (mutate)

const originalData = [1, 2, 3];
const reversed = _.reverse(originalData);
// originalData now [3, 2, 1]
// originalData === reversed 

Ramda & lodash/fp (immutable)

const originalData = [1, 2, 3];
const reversed = R.reverse(originalData);
// originalData remains the same
// originalData !== reversed 

Composition

Ramda makes it simple for you to build complex logic through functional composition - Why Ramda?

Both Ramda and lodash/fp provide a compose function, allowing you to combine multiple functions. compose performs right-to-left composition

Function composition (without compose)

const input = ['1', '2', '3', 'A'];

// Note: Number('A') returns NaN
const toNumbers = arr => arr.map(x => Number(x));
const rejectNaN = arr => arr.filter(x => !isNaN(x));
const sum = arr => arr.reduce((acc, val) => acc + val, 0);

const sumArray = arr => sum(rejectNaN(toNumbers(arr)));
sumArray(input); // 6

ES6 compose (basic)

const input = ['1', '2', '3', 'A'];

const compose = (...fns) => x =>
  fns.reduceRight((acc, fn) => fn(acc), x);

// using pervious toNumbers, rejectNaN, sum
const es6SumArray = compose(
  sum,
  rejectNaN,
  toNumbers
);
es6SumArray(input); // 6

Ramda compose

const input = ['1', '2', '3', 'A'];

const ramdaSumArray = R.compose(
  R.sum,
  R.reject(R.equals(NaN)),
  R.map(Number)
);
ramdaSumArray(input); // 6

R.compose: the rightmost function may have any arity (any number of parameters); the remaining functions must be unary (single parameter functions) - Ramda Compose

R.pipe: the same as R.compose, but function composition is performed left-to-right Ramda Pipe

FP Concepts CodeSandbox

ES6 FP Utilities

Initially I wrote FP utility functions to learn how Ramda works, but I have seen similar ones used in npm modules, where the contributors didn't want to require any external dependencies. I've seen variations of ES6 compose a number of times (Redux compose.js)

Info: The DomLog npm library used in the CodeSandbox embed is one I published myself, and you can see an example of a compose function used there

There is no auto-currying implemented in the following examples - these are simple higher-order functions with fixed arity. Also, the functions tend to focus on array transformation, whereas Ramda can transform both objects and arrays e.g. R.map

R.map(double, {x: 1, y: 2, z: 3}); //=> {x: 2, y: 4, z: 6}

Info: implementation of R.map

ES6 basic FP utilities

These are some ES6 equivalents of a few common Ramda functions - they would be used in conjuntion with compose

const compose = (...fns) => x =>
  fns.reduceRight((acc, fn) => fn(acc), x);

const map = fn => (arr = []) => arr.map(fn);

const prop = p => (obj = {}) => obj[p];

const pluck = p => arr => map(prop(p))(arr);

const flatten = (arr = []) =>
  [].concat.apply([], arr);

const isNil = val =>
  val === null || val === void 0;

const complement = fn => val => !fn(val);

const rejectNil = (arr = []) =>
  arr.filter(complement(isNil));

const flip = fn => a => b => fn(b)(a);

const identity = x => x;

const tap = fn => val => {
  fn(val);
  return val;
};

ES6 complex FP functions

groupBy is available in both Ramda and lodash/fp, but evolve is specific to Ramda. There is no simple equivalent to evolve in lodash/fp as far as I can tell

groupBy

const groupBy = p => arr =>
  arr.reduce((acc, obj) => {
    const value = obj[p];
    acc[value] = [...(acc[value] || []), obj];
    return acc;
  }, {});

Description of Ramda evolve from the documentation: Creates a new object by recursively evolving a shallow copy of object, according to the transformation functions. All non-primitive properties are copied by reference. A transformation function will not be invoked if its corresponding key does not exist in the evolved object

evolve

const evolve = transformation => obj => {
  const transformed = Object.entries(transformation).reduce(
    (acc, [key, value]) => {
      if (!obj.hasOwnProperty(key)) return acc;
      acc[key] =
        typeof value === 'function'
          ? value(obj[key])
          : evolve(value)(obj[key]);
      return acc;
    },
    {}
  );
  return { ...obj, ...transformed };
};

ES6 evolve demo:

// Evolve
const unixToISOString = unix =>
  new Date(unix).toISOString();

const record = {
  date: 799286400000, //Unix time stamp in ms
  details: {
    enabled: false,
    version: 1
  }
};

const evolveRecord = evolve({
  date: unixToISOString,
  details: {
    enabled: complement(identity)
  }
});

const newRecord = evolveRecord(record);

/*
{
  "date": 799286400000,
  "details": {
    "enabled": false,
    "version": 1
  }
}

{
  "date": "1995-05-01T00:00:00.000Z",
  "details": {
    "enabled": true,
    "version": 1
  }
}
*/

FP Utilities CodeSandbox

Brian Bishop

Dublin, Ireland
v7.2.15+sha.e7897e1