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!
When learning ramda
, I highly recommend:
node
> R = require('ramda')
> R.keys(R)
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.
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 Let's go into some examples!
__
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)
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
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)
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
)
✌️