Learning Rambda(x)

Published January 22nd, 2020 20m read time

I've been getting into functional programming as a of late, mostly due to having watched Kyle Simpson's course on Frontend Masters. I looked at a bunch of FP libraries, but the one I settled on was Rambda. It maintains most of Ramda's API but cuts out a lot of the possibly more unnecessary stuff. This single blog post is my chronicle of learning Rambda along with its extended version, Rambdax.

add/subtract

add(a: number, b: number): number
subtract(a: number, b: number): number

Well, this is an easy one to get started with. The first time I saw add as a function, I thought it was dumb, but once you realize you can do this:

const add5 = R.add(5)
const result = add5(3) // 8

The power of functional programming really starts to shine.

adjust

adjust(i: number, replaceFn: Function, arr: T[]): T[]

This isn't a function I think I'd use a ton right away, but I can see how it could be useful someday.

R.adjust(1, v v.toUpperCase(), ['brandon', 'lee', 'pittman'])
// [ 'brandon', 'LEE', 'pittman' ]

Could be useful in a loop where you needed to act on each item in the array, but then why not just use map and create a new array?

all

all(fn: Function, arr: T[]): boolean

This one reminds me of a filter-style function. Again, probably not useful on its own, but useful when combined with other utilities.

R.all(v v.length == 3, ['Bob', 'Tim', 'Sam'])

allPass

allPass(rules: Function[], input: any): boolean

First, set up rules like:

var rules = {
  x x > 30
}

var over30 = R.allPass(rules)

Then pass an object to see if it passes.

var brandon = { age: 36 }
var lisa = { age: 26 }
over30(brandon) // true
over30(lisa) // false

// Combine with all for fun and profit
var allOver30 = R.all(over30)
allOver30([brandon]) // true
allOver30([brandon, lisa]) // false

always/indentiy

always(x: any): Function
identity(x: T): T

Well, you just get a function that returns the thing you originaly gave it or the thing itself.

const brandon = R.always('Brandon')
brandon() // 'Brandon

and

It's like &&.

R.and(1 > 0, 2 > 1) // true
R.and(1 > 0, 0 > 1) // false

any

any(condition: Function, arr: T[]): boolean

Does what it says on the tin.

var greaterThan100 = x x > 100
R.any(greaterThan100, [99,100,110]) // true

anyPass

The any version of allPass.

append/prepend

append(valueToAppend: T, arr: T[]): T[]
prepend(x: T, arr: T[]): T[]

push and unshift for the FP kids.

assoc

assoc(prop: any, value: any, obj: object): object

Sets a key/value on an object. Can be broken up for flexibility.

var person = {name: "John"}
var setAge = R.assoc('age')
var setAgeTo20 = setAge(20)
setAgeTo20(person) // {name: "John", age: 20}

both/either

both(firstCondition: Function, secondCondition: Function, input: any): boolean
either(firstCondition: Function, secondCondition: Function): Function

Like allPass and anyPass, but for two functions only. Pass functions as positional parameters instead of as an array.

R.either(v R.startsWith('B', v), v R.endsWith('n', v))('Lenny') // false

clone

clone(objOrArr: T|T[]): T|T[]

Let's clone some shit, boys! Deep cloning, to boot.

compose/pipe

compose(fn1: Function, ... , fnN: Function): any
pipe(fn1: Function, ... , fnN: Function): any

Combine functions into Megatron.

complement/opposite

complement(fn: Function): Function
opposite(fn: Function): Function

If the supplied function equals true, return false, and vice versa. Well that's fun.

const fn = (name name == 'Brandon')
fn('Brandon') // false
const fn('Sammy') // true

concat

Just an FP version of the normal Array.concat().

curry

curry(fn: Function): Function

Finally! On to the serious FP stuff. Combine functions into their full FP glory.

const addFourNumbers = (a, b, c, d) a + b + c + d
const curriedAddFourNumbers = R.curry(addFourNumbers)
const f = curriedAddFourNumbers(1, 2)
const g = f(3)
const result = g(4) // 10

inc/dec

R.inc(2) // 2
R.dec(2) // 1

++ and -- for FP folks.

defaultTo

defaultTo(defaultValue: T, ...inputArguments: any[]): T

Lets you set a default value. Great for when you need a fallback.

const defaultToBrandon = R.defaultTo('Brandon')
var name
defaultToBrandon(name) // 'Brandon'
name = "Bruce Wayne"
defaultToBrandon(name) // 'Bruce Wayne'

dissoc

dissoc(prop: any, obj: object): object

Returns an object without the specified key.

var person = { name: 'Brandon', age: 36 }
const removeAge = R.dissoc('age')
removeAge(person) // { name: 'Brandon' }

divide

R.divide(100,5) // 20

drop/dropLast/take

drop(howManyToDrop: number, arrOrStr: T[]|string): T[]|String
dropLast(howManyToDrop: number, arrOrStr: T[]|String): T[]|String
take(num: number, arrOrStr: T[]|string): T[]|string
takeLast(num: number, arrOrStr: T[]|string): T[]|string

Removes the indicated number of items or characters from the provided array or string.

var numbers = [1,2,3]
R.drop(2, numbers) // [3]
R.dropLast(2, numbers) // [1]

startsWith/endsWith

startsWith(x: string, str: string): boolean
endsWith(x: string, str: string): boolean

Checks if a string starts or ends with a provided string. Regex not allowed.

var name = 'Brandon'
R.startsWith('B', name) // true
R.endsWith('n', name) // true

equals

equals(a: any, b: any): boolean

Checks equality even in objects.

R.equals(
  [1, {a:2}, [{b:3}]],
  [1, {a:2}, [{b:3}]]
) // true

F/T

Always returns false or true.

filter

filter(filterFn: Function, x: Array|Object): Array|Object

filter, but with the parameters reversed.

const filterBrandon = R.filter(v v.toLowerCase() != 'brandon')
filterBrandon(['Brandon']) // []
filterBrandon(['Brandon', 'Lisa']) // ['Lisa']

find/findIndex/findLast/findLastIndex

find(findFn: Function, arr: T[]): T|undefined
findIndex(findFn: Function, arr: T[]): number
findLast(findFn: Function, arr: T[]): T|undefined
findLastIndex(findFn: Function, arr: T[]): number

find, but with the parameters reversed. findIndex will return -1 if nothing can be matched. findLast is the inverse of find. findLastIndex is the inverse of findIndex.

var fn = v v > 5
R.find(fn, [1,2,3,4,6]) // 6

flatten

flatten(arr: any[]): any[]

Like a pancake, yo.

R.flatten([ 1, [ 2, [ 3 ] ] ]) // [ 1, 2, 3 ]

flip

flip(fn: Function): Function

Returns a copy of the provided function with its parameters in reverse.

const subtractFlip = R.flip(R.subtract)
const result = subtractFlip(1,7)// 6

forEach

forEach(fn: Function, x: Array|Object): Array|Object

Good ol' forEach for FP folks.

fromPairs

fromPairs(list: any[]): object

Equivalent to Object.fromEntries.

groupBy

groupBy(fn: Function, arr: Array): Object

Takes the supplied function to test array items and assign to generated key.

people = [{first: 'Brandon', last: 'Pittman'}, {first: 'Lisa', last: 'Jones'}]
R.groupBy(x x.last.toLowerCase(), people)
// {
//  Pittman: [ { first: 'Brandon', last: 'Pittman' } ],
//  Jones: [ { first: 'Lisa', last: 'Jones' } ]
// }

groupWith

groupWith(fn: Function, arr: Array): Array

Takes the supplied function to test array items and group them when they satisfy the predicate function.

has

has(prop: string, obj: Object): boolean

Check is an object has a particular property.

R.has('name', {name: 'Brandon'}) // true

head/tail

head(arrOrStr: T[]|string): T|string
tail(arrOrStr: T[]|string): T[]|string

Head to tail carnivores favorite functions.

identical

identical(a: any, b: any): boolean

Is we both the same ting?

ifElse

ifElse(condition: Function|boolean, ifFn: Function, elseFn: Function): Function

Evaluates second fn if first fn is true, else it evaluates the third fn.

var fn = R.ifElse(
  x x.toLowerCase() == 'brandon',
  _ 'Cool guy',
  _ 'Not as cool'
)
fn('Brandon') // "Cool guy"

includes

includes(valueToFind: T|string, input: T[]|string): boolean

Next level includes.

R.includes('oo', 'foo') // true
R.includes({a: 1}, [{a: 1}]) // true

indexBy

indexBy(condition: Function|String, arr: T[]): Object

Generates an object with items from array that match the provided function or object path string.

const arr = [ {id: 1}, {id: 2} ]
const result = R.indexBy(
  x x.id,
  arr
)
const pathResult = R.indexBy(
  'id',
  arr
)
// { 1: {id: 1}, 2: {id: 2} }

indexOf/lastIndexOf

indexOf(valueToFind: any, arr: T[]): number
lastIndexOf(x: any, arr: T[]): number

Like findIndex for a specific value, not a function. Returns -1 if not found.

R.indexOf(1, [1, 2]) // 0
R.indexOf(0, [1, 2]) // -1

init

init(arrOrStr: T[]|string): T[]|string

Opposite of tail().

R.init([1, 2, 3])  // [1, 2]
R.init('foo')  // 'fo'

is/isNil/isEmpty

is(xPrototype: any, x: any): boolean
isNil(x: any): boolean
isEmpty(x: any): boolean

is checks against instanceof, but doesn't seem to work with Array.

R.is(String, 'foo') // true

join

oin(separator: string, arr: T[]): string

Good ol' join.

keys

keys(x: Object): string[]

Object.keys()

last

last(arrOrStr: T[]|string): T|string

Opposite of head.

length

length(arrOrStr: Array|String): Number

The usual length.

map

map(mapFn: Function, x: Array|Object): Array|Object

map for FP people. Works on Objects as well.

match

match(regExpression: Regex, str: string): string[]

String.match, yo.

max/maxBy/min/minBy

max(x: Number|String, y: Number|String): Number|String
maxBy(fn: Function, x: Number|String, y: Number|String): Number|String
min(x: Number|String, y: Number|String): Number|String
minBy(fn: Function, x: Number|String, y: Number|String): Number|String

Self-explanatory.

merge/mergeRight

merge(a: Object, b: Object)
mergeRight(a: Object, b: Object)

Object.assign.

modulo

modulo(a: number, b: number):numberNumber

Remainder of a/b.

multiply

multiply(a: number, b: number): number

You know…

not

not(x: any): boolean

! as a function.

R.not(true) // false

## omit

```typescript
omit(propsToOmit: string[]|string, obj: Object): Object

R.dissoc for multiple keys.

R.omit('a,c,d', {a: 1, b: 2, c: 3}) // {b: 2}

path

path(pathToSearch: string[]|string, obj: Object): any

Grab a value from an object by path string.

R.path('a.b', {a: {b: 1}}) // 1

pathOr

pathOr(defaultValue: any, pathToSearch: string[]|string, obj: Object): any

Like path but lets you provide a default value, kinda like defaultTo.

R.pathOr(1, 'a.b', {a: {b: 2}}) // 2
R.pathOr(1, ['a', 'b'], {a: {b: 2}}) // 2
R.pathOr(1, ['a', 'c'], {a: {b: 2}}) // 1

partial

partial(fn: Function, ...inputs: any[]): Function | any

Like curry, but you can pre-supply arguments.

const fn = (salutation, title, firstName, lastName) {
  salutation + ', ' + title + ' ' + firstName + ' ' + lastName + '!'
}

const canPassAnyNumberOfArguments = partial(fn, 'Hello', 'Ms.')
const finalFn = canPassAnyNumberOfArguments('foo')

finalFn('bar') //  'Hello, Ms. foo bar!'

partialCurry

partialCurry(fn: Function|Async, partialInput: Object, input: Object): Function|Promise

It's like partial, but it lets you use an object and its keys instead of positional parameters.

var fn = ({first, middle, last}) `${first} ${middle} ${last}`
var start = R.partialCurry(fn, {first: 'Brandon'})
var final = start({middle: 'Lee', last: 'Pittman'}) // "Brandon Lee Pittman"

pick

pick(propsToPick: string[], obj: Object): Object

An grab just what you need from an object.

me = {first: 'Brandon', middle: 'Lee', last: 'Pittman'}
R.pick(['first', 'last'], me)
// { first: 'Brandon', last: 'Pittman' }

pluck

pluck(property: string, arr: Object[]): any[]

Kinda like pick, but you're pulling specific key off of each object in an array.

b = {first: 'Brandon', last: 'Pittman'}
l = {first: 'Lisa', last: 'Jones'}
people = [b,l]
R.pluck('first', people) // [ 'Brandon', 'Lisa' ]

prop

prop(propToFind: string, obj: Object): any

Object[prop] as a Function.

b = {first: 'Brandon', last: 'Pittman'}
R.prop('first', b) // "Brandon"

propEq

propEq(propToFind: string, valueToMatch: any, obj: Object): boolean

Checks if the provided prop equals the value given against a provide object.

b = {first: 'Brandon', last: 'Pittman'}
R.propEq('first', 'Brandon', b) // true

propIs

propIs(type: any, name: string, obj: Object): boolean

Checks type of prop.

b = {first: 'Brandon', last: 'Pittman'}
R.propIs(String, 'first', b)

propOr

propOr(defaultValue: any, param: string, obj: Object): any

prop() but with a fallback.

R.propOr('Brandon Pittman', 'name', b) // "Brandon Pittman"

range

range(start: number, end: number): number[]

Returns an array of numbers starting with start and going up to end.

R.range(0,3) // [0,1,2]

reduce

reduce(iteratorFn: Function, accumulator: any, array: T[]): any

Good ol' reduce.

reject

reject(fn: Function, arr: T[]): T[]

Opposite of filter.

repeat

repeat(valueToRepeat: T, num: number): T[]

Works like Ruby's #times method.

R.repeat('brandon', 2) // ['brandon', 'brandon']

replace

replace(strOrRegex: string|Regex, replacer: string, str: string): string

String.replace(), essentially.

reverse

reverse(str: T[]): T[]

String.reverse(), essentially.

slice

slice(list: T[], from: Number, to: Number)

Array.prototype.slice!!!

sort

sort

Array.prototype.sort!!!

sortBy

sortBy

Sort but can take object props.

split

split(separator: string, str: string): string[]

String.split()

splitEvery

splitEvery(sliceLength: number, arrOrString: T[]|string): T[T[]]|string[]

String.split, but split by length rather than a specific string.

sum

sum(listOfNumbers: number[]): number

Handy sum so you don't need to write your own with reduce.

tap

tap(fn: Function, input: T): T

Runs the function but returns the input.

test

test(regExpression: Regex, str: string): boolean

Like an assertion.

times

times(fn: Function, n: number): T[]

Runs the function n number of times. Passes the current iteration number to the function.

R.times(R.identity, 5) // [0, 1, 2, 3, 4]

toLower/toUpper

toLower(str: string): string
toUpper(str: string): string

Saving characters.

toPairs

toPairs(obj: object): any[]

Object.toEntries().

toString

toString(x: any): string

Yup.

transpose

transpose(input: Array): Array

Spreads arrays out across new arrays.

const input = [[10, 11], [20], [], [30, 31, 32]]
const expected = [[10, 20, 30], [11, 31], [32]]
const result = R.transpose(input)
// result === expected

trim

trim(str: string): string

String.trim()

type

type(a: any): string

Returns type of argument; is async function and promise aware.

uniq/uniqWith

uniq(arr: T[]): T[]
uniqWith(fn: Function, arr: T[]): T[]

Returns an array with just uniq elements. No more Array.from(new Set(...))!

R.uniq([1, 1, 2, 1]) // [1, 2]

uniqWith

uniqWith(fn: Function, arr: T[]): T[]

Like adjust, without needing a function.

R.update(0, 'foo', ['bar', 'baz']) // ['foo', baz]

values

values(obj: Object): Array

Object.values()

without

without(a: T[], b: T[]): T[]

Return array minus the supplied values.

R.without([1, 2], [1, 2, 3, 4])
// [3, 4]

zip/zipObj

zip(a: K[], b: V[]): Array

Unlike transpose, zip will end after the shortest list is done.

R.zip([1, 2, 3, 4], ['A', 'B'])
// [[1, 'A'], [2, 'B']]
R.zipObj(['a', 'b', 'c'], [1, 2])
// {a: 1, b: 2}

Review

Whew! That wraps up all the function is Rambda. A lot of them are utility functions you've used in vanilla JS or with another utility library like Lodash. There aren't a ton of FP-specific functions, but the way they are structured, they are perfect for programming in a functional style. Now, onto the stuff just in Rambdax. Those functions are the more interesting ones as far as I'm concerned.

{all,any}{False,True,Type}

allFalse(...inputs: any|predicate[]): boolean
allTrue(...inputs: any|predicate[]): boolean
allType(targetType: string): (...inputs: any[]) => boolean
anyFalse(...inputs: any|predicate[]): boolean
anyTrue(...inputs: any|predicate[]): boolean
anyType

Acts like a filter function for various cases.

change

change(origin: object, path: string, changeData: any): object

Update an object using a path syntax. Will dynamically create sub-keys as needed.

var me = {}
R.change(me, 'name.last', 'Pittman')
// { name: { last: 'Pittman' } }

composeAsync/pipedAsync

composeAsync(...fns: Array<Function|Async>)(startValue: any): Promise
pipedAsync(input: any, ...fns: Array<Function|Async>): Promise

Like compose, but supports using async functions.

composed/piped

composed(...fnList: any[]): any
piped(...fnList: any[]): any

compose but lets you pass the input as the final parameter. pipe but lets you pass the input as the first parameter.

const result = composed(
  R.map(x => x*10),
  R.filter(x => x > 1),
  [1,2,3]
)
// => [20, 30]

debounce

debounce(fn: Function, ms: number): any

Maybe someday I'll remember the difference between this and throttle.

Creates a debounced function that delays invoking fn until after wait milliseconds ms have elapsed since the last time the debounced function was invoked.

defaultToStrict

defaultToStrict(defaultValue: T, ...inputArguments: any[]): T

Like the regular defaultTo, but ya know, stricter.

R.defaultToStrict('foo', undefined) // => 'foo'
R.defaultToStrict('foo', 1) // => 'foo'
R.defaultToStrict('foo', {}) // => 'foo'
R.defaultTo('foo', undefined, 1, [], {}) // => 'foo'
R.defaultTo('foo', undefined, 1, [], {}, 'bar') // => 'bar'

defaultToWhen

defaultToWhen(fallback: any, fn: Function, ...inputArguments: any[]): any

Returns the fallback if no argument passes the predicate function.

const fn = x => x > 2
const fallback = 10
const result = R.defaultToWhen(fallback, fn, 1,6,8,0 )
// result is 6

delay

delay(ms: number): Promise

It's a wait function. Should be used in an async function.

const result = R.delay(1000)
// `result` resolves to `'RAMBDAX_DELAY'`

filterAsync

filterAsync(predicate: Async, iterateOver: object|array): Promise

filter, but async.

findInObject

findInObject(fn: Function, obj: object): object

Like Array.find but for objects. Only returns the first match.

b = { first: "Brandon" }
R.findInObject((v,k) => v.length > 6, b)
// { prop: 'first', value: 'Brandon' }

getter/setter/reset

getter(key: undefined|string|string[]): any
setter(key: string|object, value?: any): void
reset(): void

Use Rambdax's cache object.

R.setter('name', 'Brandon')
R.getter('name') // "Brandon"
R.reset() // everything's undefined now

glue

glue(input: string, glueString?: string): string

It's like join but for strings.

hasPath

hasPath(input: string|string[], input: object): boolean

Checks to see if the provided path exists on the object.

headObject

headObject(input: object): {prop: string, value: T}

Takes an object with just one key and returns a new object with a prop and value set of keys.

var double = x => x * 2
R.headObject({double}) // { prop: 'double', value: [Function] }

includesType

includesType(targetType: string, list: any[]): boolean

Array.includes but checks if a type is the same.

const result = R.includesType(
  'String',
  [1,2,'foo']
) // true

inject

inject(injection: string, marker: string, str: string, beforeFlag: boolean): string

Injects the inject string after (or before with true) the MARKER location. Seems like it would be better to replace the marker.

R.inject('Lee', 'MIDDLE', 'Brandon MIDDLE Pittman', true) // "Brandon LeeMIDDLE Pittman"

isAttach

isAttach(): boolean

Bolts on an is method to object-like variables to let you check type.

R.isAttach()
const foo = [1,2,3]
const result = foo.is(['number']) // => true

isFalsy

isFalsy(x: any): boolean

Checks if falsy. Oddly, no isTruthy to be found.

isFunction

isFunction(x: any): boolean

isNil

isNil(x: any): boolean

null or undefinded?

isPromise

isPromise(x: any): boolean

isType

isType(xType: string, x: any): boolean

Seems to be the same as R.is.

isValid

isValid({ input: object: schema: object }): boolean

Lets you run validation checks against a schema! Simply returns false if not valid. Doesn't throw.

Full isValid docs.

Also, superstruct is a thing.

// as of 2020-01-28
var brandon = {name: 'Brandon Pittman', married: false}
R.isValid({input: brandon, schema: { name: String, married: Boolean}})

maybe

maybe(ifRule: Boolean, whenIf: T, whenElse: T): T

Acts like ternary operator. Recommended when dealing with nested ternary operator usage. Doesn't require the stems to be functions.

const x = 4
const y = 8
const result = R.maybe(
  x > 2,
  y > 10 ? 3 : 7,
  5
) // result == 7

mapAsync/mapFastAsync

mapAsync(fn: Async|Promise, arr: Array): Promise
mapFastAsync(fn: Async|Promise, arr: Array): Promise

Async mapping operations; sequentially or in parallel. Best used with composeAsync.

memoize

memoize(fn: Function|Promise): any

Returns cached result of provided function.

mergeAll

mergeAll(input: Object[]): Object

Merges all objects in the array sequentially.

mergeDeep

mergeDeep(slave: object, master: object): object

Deeply merge objects, master object's keys win.

nextIndex

nextIndex(index: number, list: any[]): number

Returns the next index of the list, but loops back to 0 when reaching the end.

ok

ok(...inputs: any[]): (schemas: any[]) => true | Error

R.isValid-style checks, but throws error if validation fails.

const result = R.ok(
  1, [ 'foo', 'bar' ]
)('number', [ 'string' ])
// true

once

once(fn: Function): Function

Returns a function that can only ever be invoked once.

otherwise

otherwise(fallback: Function, toResolve: Promise): Promise

Catches and handles errors inside compose and pipe calls. Returns identity if no error occurs.

pathEq

pathEq(path:string|string[], target: any, obj: object): boolean

Checks for equality at path.

const result = R.pathEq(
  'a.b',
  1,
  {a: {b:1} }
) // true

pass

pass(...inputs: any[]): (schemas: any[]) => boolean

Like R.ok, but doesn't throw, just returns false.

partition

partition(predicate: Function, input: Array|Object): [Array|Object, Array|Object]

Like filter, but returns two arrays — one with passing items and one with rejected items.

produce

produce(conditions: Object, input: any): Promise|Object

Passes input to each function in the object.

const conditions = {
  foo: a => a > 10,
  bar: a => ({baz:a})
}

const result = R.produce(conditions, 7)

const expectedResult = {
  foo: false,
  bar: {baz: 7}
} // result === expectedResult

promiseAllObject

promiseAllObject(promises: Object): Promise

Loops over promises in an object.

random

random(min: number, max: number): number

Returns a random number. Duh.

remove

remove(inputs: string|RegExp[], text: string): string

Removes each string in the provided array from the input text.

const result = remove(
  ['foo','bar'],
  'foo bar baz foo'
) // 'baz foo'

renameProps

renameProps(rules: Object, input: Object): Object

Renames the keys on an object.

const rules = {
  f: "foo",
  b: "bar"
}
const input = {
  f:1,
  b:2
}
const result = R.renameProps(rules, input)
const expectedResult = {
  foo:1,
  bar:2
} // result === expectedResult

resolve

resolve(afterResolve: Function, toResolve: Promise): Promise

Turns a composition asynchronous.

const expected = {
 firstName : 'FIRST_NAME_FOO',
 lastName  : 'LAST_NAME_FOO',
}

const fetchMember = async x => {
 await R.delay(200)

 return {
   a         : 1,
   firstName : `FIRST_NAME_${ x.query }`,
   lastName  : `LAST_NAME_${ x.query }`,
 }
}

const getMemberName = pipe(
 email => ({ query : email }),
 fetchMember,
 resolve(pick('firstName,lastName'))
)
const result = await getMemberName('FOO')
// result === expected

s

s(): undefined

Lets you chain function calls.

R.s() // true
"foo".s(R.toUpper) // "FOO"

sortObject

sortObject(predicate: Function, obj: Object): Object

Allows you to sort an object using its props and values.

const predicate = (propA, propB, valueA, valueB) => valueA > valueB ? -1 : 1
const sorted = R.sortObject(predicate, {a:1, b: 4, c: 2})
// {b:4, c: 2, a:1}

shuffle

shuffle(arr: T[]): T[]

Just like Icky Woods.

switcher

A chainable switch statement. Must have a .default() call at the end.

const valueToMatch = {foo: 1}

const result = R.switcher(valueToMatch)
  .is('baz', 'is baz')
  .is( x => typeof x === 'boolean', 'is boolean')
  .is({foo: 1}, 'Property foo is 1')
  .default('is bar')

console.log(result) // => 'Property foo is 1'

tapAsync

tapAsync(fn: Function|Async|Promise, inputArgument: T): T

Like tap, but accepts an async function.

template

template(input: string, templateInput: object): string

Lets you to Handlebars-style templating with the input providing the values to evaluate.

Currently waiting to see if PR is accepted to allow for blanket JS evaluations.

toDecimal

toDecimal(num: number, charsAfterDecimalPoint: number): number

Convert number to decimal number.

R.toDecimal(2.45464,2) // 2.45

throttle

throttle(fn: Function, period: number): Function

Creates a function that will only be called once every n milliseconds.

tryCatch

tryCatch(fn: Async|Function, fallback: any): Function

Will return fallback if fn throws an error.

unless

unless(rule: Function|boolean, whenFalse: Function|any): Function

Inverse of an if.

const result = R.unless(
  R.isNil,
  R.inc
)(1) // 2

wait

wait(fn: Async): Promise<[any, Error]>

Returns a promise that resolves to an array with a return value and an error, a la Go.

void async function wait(){
  const [result, err] = await R.wait(R.delay(1000))
  // => err is undefined
  // => result is `RAMBDAX_DELAY`
}()

waitFor

waitFor(condition: any, ms: number): Promise

Gives you a promise that resolves to true if the provided function returns true without the alloted amount of time.

where

where(conditions: object, input: object): boolean

Returns a function that takes an object and validates each prop.

const condition = R.where({
  a : aProp => typeof aProp === "string",
  b : bProp => bProp === 4
})

const result = condition({
  a : "foo",
  b : 4,
  c : 11,
}) // true

whereEq

whereEq(rule: object, input: any): boolean

Validates object values against a rule object.

when

when(rule: Function|boolean, whenTrue: Function|any): Function

If as a Function™. Passes input value through if it doesn't meet the rule.

const truncate = R.when(
  x => x.length > 5,
  R.compose(x => `${x}...`, R.take(5))
)

const result = truncate('12345678')
// '12345...'

whenAsync

whenAsync(rule: condition: Async | Function | boolean, whenFn: Async | Function): Promise

Async version of when.

Wrap-Up

Well, I did it. I went through all the functions. Did I memorize them all? Probably not. But I learned a great deal about the library and I'm starting to see places in my own code where I could use it more and more. I even made a PR to enhance the template function. Sadly, the response time from the maintainer is a little slower than I'd like. I also found a handful of places in the docs where they weren't 100% accurate. Rambda doesn't have the user base that Ramda does, so it's natural that the docs don't get quite the same level of attention, but hopefully, Rambda continues receiving love and care in the future.