ramda

Published: 1/16/2020
Last edited: 1/26/2020
A practical functional library for JavaScript programmers
yarn add ramda 

ramda is essential. If I'm going to write any code of any real utility, I'm most likely to reach for ramda to create clean transformational pipelines. All of its functions are automatically curried and it never mutates data.

Part of what I really like about it is that you can use it to compose simple and pure functions to get great complexity. Look at how each function in the capitalize function below is pure and very easy to reason about.

import {
  ap,
  curry,
  join,
  map,
  pipe,
  slice,
  split,
  toLower,
  toUpper
} from 'ramda'

const capitalize = curry(
  (joint, xxx) => pipe(
    split(' '),
    map(pipe(
      z => [z], // "box"
      ap([
        slice(0, 1),
        slice(1, Infinity)
      ])
    )),
    map(
      // note how we're consuming an array here!
      ([a, lphabet]) => (
        toUpper(a) + toLower(lphabet)
      )
    ),
    join(joint)
  )(xxx)
)

capitalize('! ', 'ramda is very cool')

Oooh tacit goodness!

Learning

When learning ramda, I highly recommend:

  1. Playing in the node REPL:
node
> R = require('ramda')
> R.keys(R)
  1. When sharing code with others, use the online Ramda REPL for simple sandboxing
  2. Install eslint-plugin-ramda for some automatic (though a few are unhelpful) refactoring suggestions via eslint.

NB: ramda has 255 functions within it. I'd say I use a maximum of about ~30 of them. Don't get overwhelmed by the sheer size of ramda and instead focus on the essentials.

Essential ramda

  • __ It's a magic placeholder!
  • curry Defer parameters for when you have them! Partial application FTW.
  • pipe Function composition!
  • map Ooh what a functor!
  • propOr Grab a property or a default value! Safety!
  • pathOr Grab a nested property or a default value! Extra safety!
  • ap Segment your data cleanly and divine lines through it.
  • propEq Assert property equivalence to static values!
  • propSatisfies Assert whatever you want with a predicate function!
  • ifElse When your tacit pipelines need conditional branching!
  • not Invert boolean values in your pipes!
  • complement Invert the outcome from a given function

Examples

Let's go into some examples!

Specify gaps with a magic placeholder __

The magic placeholder allows you to define gaps in a curried function.

import { curry, __ } from 'ramda'
const ternary = curry((a, b, c) => a + b / c)
const sumOver3 = ternary(__, __, 3)

You can also get the same functionality by playing with flip, but IMO this is slightly harder to read / reason about.

import { __, curry } from 'ramda'
const divide = curry((a, b) => a / b)
// divide(3) === 3 / x (not super useful)
const over3 = divide(__, 3) === x / 3
// identical:
// import {flip} from 'ramda'
// flip(divide)(3) === divide(__)(3)
Safe property access with pathOr / propOr

Safely access properties! Remember the order here, it's default value, followed by property or path-to-property, followed by the object / whatever itself.

const eventTargetValue = pathOr(false, ['target', 'value'])
const orFalse = propOr(false)
orFalse('someProperty', {}) // false
Apply with ap

ap applies a list of functions to a list of values. NB In most cases, remember to encase your values in an array beforehand! I always refer to this as box but you do you.

import { ap, pipe, pathOr } from 'ramda'
// this is the easiest thing to forget --
// if you don't box before you `ap` you will
// likely not have the data you expect in your output
const box = x => [x]
const data = {
  a: { aa: { aaa: 111 } },
  b: { bb: { bbb: 222 } },
  c: { cc: { ccc: 333 } }
}

const safeNestedAccessors = pipe(
  // remember to box before you ap
  box,
  ap([
    pathOr(-1, ['a', 'aa', 'aaa']),
    pathOr(-2, ['b', 'bb', 'bbb']),
    pathOr(-3, ['c', 'cc', 'ccc']),
    pathOr(1, ['d', 'dd', 'ddd'])
  ]),
  ([a, b, c, d]) => `nested sum: ${a + b + c / d}`
)

safeNestedAccessors(data)
Assert stuff with propEq / propSatisfies / ifElse

Use propEq to compare static values, and propSatisfies to compare via a function which receives the property as its input. Use ifElse to create conditional branches while still using tacit pipes.

import {
  identity,
  ifElse,
  not,
  pipe,
  propEq
} from 'ramda'

// ifElse takes three unary functions which all receive the same input
const goodOrBad = ifElse(
  // input => if the input.status !== 200
  pipe(propEq('status', 200), not),
  // input => return the input with an error value
  input => ({ input, error: true }),
  // return input
  identity
)

Here's the same thing again but using propSatisfies and complement instead of propEq and not, respectively. Note the order of parameters for propSatisfies, the predicate comes before the property.

import {
  complement,
  equals,
  identity as I,
  mergeRight,
  ifElse,
  pipe,
  propSatisfies
} from 'ramda'

const goodOrBad = ifElse(
  // input => if the input.status !== 200
  // propSatisfies(x => x !== 200, 'status') // equivalent
  propSatisfies(complement(equals)(200), 'status')),
  mergeRight({ error: true }),
  I
)

✌️

Published: 1/16/2020
Last edited: 1/26/2020
Content by brekk
See this page on Github
Built with 💛 by Open Sorcerers
Created with Gatsby using the 😎 foresight starter