unmutable

An immutable, functional data collection library for plain old Javascript.

It combines my favourite parts of immutable.js, lodash/fp and ramdajs together.

Unmutable follows immutable.js's API almost exactly, and but exports each method individually as a point-free function, in the same way as lodash/fp does. These functions are all immutable, and can be composed together easily.

Unmutable also works seamlessly on plain Javascript objects and arrays, Immutable.js Maps, Lists and Records.

Installation

npm install unmutable --save
or
yarn add unmutable

Usage

Use it in a pipe!
var data = [1,2,3];

let result = pipeWith(
    data,
    push(4),
    reverse(),
    map(num => num * 10)
);

// result is [40,30,20,10]
// wow!
Use it partially applied!
var data = {
    foo: 1,
    bar: null,
    baz: 3
};

var filterNulls = filter((val) => {
    return val === null;
});

let result = filterNulls(data);

// result is {foo: 1, baz: 3}
// golly!
Use it in one line!
var data = [1,3,3,2,1];

let result = unique()(data);

// result is [1,3,2]
// yee!

API Documentation

All functions can be imported from unmutable/<functionName>

import getIn from 'unmutable/getIn';
import map from 'unmutable/map';
import filter from 'unmutable/filter';
Functions - Persistent changes
set
set(key: string|number, value: any) => (collection) => newCollection

Returns a new collection also containing the new key, value pair. If an equivalent key already exists in this collection, it will be replaced.

When used with an indexed data type, key may be a negative number, which indexes back from the end of the collection.

If index larger than size, the returned collections's size will be large enough to include the index.

Description from Immutable.js' docs.
delete
delete(key: string|number) => (collection) => newCollection

Returns a new collection which excludes this key.

When used with an indexed data type, this returns a new collection which excludes this index and with a size 1 less than this collection. Values at indices above index are shifted down by 1 to fill the position.

Description from Immutable.js' docs.
deleteAll
deleteAll(key) => (collection) => newCollection

Returns a new collection which excludes the provided keys.

Description from Immutable.js' docs.
pick
pick(keys: string[]|number[]) => (collection) => newCollection

Returns a new collection which excludes all keys that aren't listed in keys.

insert
Description coming soon
clear
clear() => (collection) => newCollection

Returns a new collection containing no keys or values.

Description from Immutable.js' docs.
push
push(...values: Array<any>) => (collection) => newCollection

Returns a new collection with the provided values appended, starting at this collection's size.

Description from Immutable.js' docs.
pop
pop() => (collection) => newCollection

Returns a new collection with a size ones less than this collection, excluding the last index in this collection.

Description from Immutable.js' docs.
unshift
unshift(...values: Array<any>) => (collection) => newCollection

Returns a new collection with the provided values prepended, shifting other values ahead to higher indices.

Description from Immutable.js' docs.
shift
shift() => (collection) => newCollection

Returns a new collection with a size ones less than this collection, excluding the first index in this collection, shifting all other values to a lower index.

Description from Immutable.js' docs.
move
move(fromIndex: number, toIndex: number) => (collection) => newCollection

Moves the element at fromIndex to the position of toIndex.

update
update(key: string|number, notSetValue: any, updater: (value: any) => any) => (collection) => newCollection update(key: string|number, updater: (value: any) => any) => (collection) => newCollection update(updater: (collection: any) => any) => (collection) => newCollection

Returns a new collection having updated the value at this key with the return value of calling updater with the existing value.

Description from Immutable.js' docs.
updateInto
updateInto(key: string|number, updater: (collection) => newValue) => (collection) => newCollection

Passes collection to updater, and will set collection[key] to the result of the updater. In practise it works a bit like update(key, updater), but in this case updater receives collection instead of collection[key].

merge
merge(...otherCollections: Array<Collection>) => (collection) => newCollection

Returns a new collection resulting from merging the provided otherCollections into this collection. In other words, this takes each entry of each otherCollection and sets it on this collection.

Description from Immutable.js' docs.
mergeWith
mergeWith( merger: (oldValue: any, newValue: any, key: string) => value, ...otherCollections: Array<Collection>) => (collection) => newCollection )

Like merge(), mergeWith() returns a new collection resulting from merging the provided otherCollections into this collection, but uses the merger function for dealing with conflicts.

Description from Immutable.js' docs.
mergeDeep
Description coming soon
mergeDeepWith
Description coming soon
defaults
Description coming soon
setSize
setSize(size: number) => (collection) => newCollection

Returns a new collection with size size. If size is less than this collection's size, the new collection will exclude values at the higher indices. If size is greater than this collection's size, the new collection will have undefined values for the newly available indices.

Description from Immutable.js' docs.
rename
rename(oldKey: string|number, newKey: string|number) => (collection) => newCollection

Changes the key of oldKey to the key of newKey

swap
swap(keyA: string|number, keyB: string|number) => (collection) => newCollection

Swaps the values at the given keys. Keys that don't exist are assumed to have a value of undefined.

rotate
rotate(shift: number) => (collection) => newCollection

Rotates the elements in arrays and Lists around, according to the value of shift. A positive shift will move elements to the left, appending rotated elements to the end of the array, where as a negative shift will move elements to the right, prepending rotated elements to the start of the array.

unit
unit(otherCollection) => (collection) => newCollection

Attempts to turn otherCollection into collections data type, and returns newCollection.

setMutate
setMutate(key: string|number, value: any) => (collection) => alteredCollection

Like set() but performs mutations on value for objects and arrays. It is less expensive than set().

deleteMutate
deleteMutate(key: string|number, value: any) => (collection) => alteredCollection

Like delete() but performs mutations on value for objects and arrays. It is less expensive than delete().

Functions - Deep persistent changes
setIn
Description coming soon
deleteIn
Description coming soon
updateIn
Description coming soon
mergeIn
Description coming soon
mergeDeepIn
Description coming soon
Functions - Sequence algorithms
concat
Description coming soon
map
map(mapper: (value: any, key: string|number, original: any) => newValue) => (collection) => newCollection

Returns a new collection with values passed through a mapper function.

Description from Immutable.js' docs.
mapKeys
mapKeys(mapper: (key: string|number, value: any, original: any) => newKey) => (collection) => newCollection

Returns a new collection with keys passed through a mapper function.

Description from Immutable.js' docs.
mapEntries
mapEntries(mapper: ([key, value]: any, index: number, original: any) => [newKey, newValue]) => (collection) => newCollection

Returns a new collection with key value pairs passed through a mapper function.

Description from Immutable.js' docs.
flatMap
Description coming soon
filter
Description coming soon
flip
flip() => (collection) => newCollection

Returns a new collection with its key values pairs flipped, so values become keys and keys become values

Description from Immutable.js' docs.
zip
Description coming soon
zipAll
Description coming soon
zipWith
Description coming soon
filterNot
Description coming soon
reverse
Description coming soon
sort
Description coming soon
sortBy
Description coming soon
stableSortBy
stableSortBy(comparatorValueMapper: value => valueToCompare, comparator?: Function) => (collection) => newCollection

Like sortBy(), but guarantees that items are stable sorted, meaning that the order of items that produce equivalent sort values are retained.

groupBy
Description coming soon
chunk
chunk(size: number) => (collection) => newCollection

Returns an array of 'chunks'. This function splits collection up into chunks, where each chunk is of the same type as collection, and contains size number of values.

chunkBy
chunkBy(predicate: Function) => (collection) => newCollection

Returns an array of 'chunks'. This function splits collection up into chunks, where the size of each chunk is determined by the predicate. It iterates over collection and calls predicate for each item on the collection. Whenever predicate returns true, a new chunk is started. It returns an array containing all the chunks that were created.

deal
deal(groups: number) => (collection) => newCollection

Returns an array of 'chunks'. This function iterates over collection, dividing it into the number of groups specified by the groups argument. It works in a similar way to someone dealing out cards to a number of players, putting the first item in the first group, the second item in the second group etc. Once the last group is reached, the next item is put in the first group again, and the deal continues cyclically until no items are left.

keyBy
keyBy(keyer: (value) => string) => (value) => {[key: string]: value} // Object

Iterates over collection and calls keyer on each item, and using the result as a key on the output object. TThe corresponding value of each key is the last element responsible for generating the key.

unique
unique() => (collection) => newCollection

Filters collection so that any element with a duplicate value is filtered out. If the value is a collection it is compared deeply.

uniqueBy
uniqueBy(getter: (collection) => any) => (collection) => newCollection

Filters collection according to the result of getter, so that any element with a duplicate result of getter is filtered out. If a collection is returned from getter, it is compared deeply.

Functions - Conversion to JavaScript types
toArray
Description coming soon
toIndexed
toIndexed() => (collection) => newCollection

Converts plain Javascript data types into arrays, and converts Immutable.js objects into Lists

toJS
Description coming soon
toJSON
toJSON() => (collection) => any

Turns the collection into plain Javascript if it is an Immutable.js data type. Internally if collection is a List then .toArray() is called, and if collection is a Map then toObject() is called.

toKeyed
toKeyed() => (collection) => newCollection

Converts plain Javascript data types into objects, and converts Immutable.js objects into Maps.

toObject
Description coming soon
toEntries
toEntries() => (collection) => newCollection

Converts collections into arrays of key, value tuples.

Functions - Reading values
get
Description coming soon
has
Description coming soon
includes
Description coming soon
first
Description coming soon
last
Description coming soon
Functions - Combination
interpose
Description coming soon
interleave
Description coming soon
splice
Description coming soon
flatten
Description coming soon
pivot
pivot() => (collection) => newCollection

Pivots the collection. The keys at the first level of nesting are moved to the second level, and the keys of the second level are moved to the first.

Functions - Search for value
indexOf
Description coming soon
lastIndexOf
Description coming soon
findIndex
Description coming soon
findLastIndex
Description coming soon
find
Description coming soon
findEntry
Description coming soon
findLast
Description coming soon
findLastEntry
Description coming soon
findKey
Description coming soon
findLastKey
Description coming soon
keyOf
Description coming soon
lastKeyOf
Description coming soon
max
Description coming soon
maxBy
Description coming soon
min
Description coming soon
minBy
Description coming soon
Functions - Value equality
equals
`equals(otherValue) => (value) => boolean`

Returns true if value and otherValue are deeply equal, or false otherwise.

notEquals
notEquals(otherValue) => (value) => boolean

Returns true if value and otherValue are not deeply equal, or false otherwise.

strictEquals
strictEquals(otherCollection: any) => (collection: any) => number

Checks if collection and otherCollection are strictly equal. This complements equals(), which checks for deep value equality.

shallowEquals
shallowEquals(otherCollection: any) => (collection: any) => number

Checks if collection and otherCollection are shallowly equal, using strict equality.

hashCode
Description coming soon
equalsType
`equalsType(otherValue: any) => (value: any) => boolean`

Returns true if value and otherValue are of the same type, or false otherwise. Class instances only count as equal if they are instances of the same class. Also unlike the type returned by typeof, null is not equal to objects, and are only equal to null.

Functions - Reading deep values
getIn
Description coming soon
hasIn
Description coming soon
Functions - Iterators
keys
Description coming soon
values
Description coming soon
entries
entriesReverse() => (collection) => Iterator

Works just like entries(), but iterates in the reverse order.

entriesReverse
Description coming soon
Functions - Iterator to array
keyArray
keyArray() => (collection) => Array<key>

Returns an array of keys on the value. Immutable.js has no function that does this, they have keys() which returns an iterator, and keySeq() which returns an Immutable.js Seq.

valueArray
valueArray() => (collection) => Array<any>

Returns an array of values on the collection. Immutable.js has no function that does this, they have values() which returns an iterator, and valueSeq() which returns an Immutable.js Seq.

entryArray
entryArray() => (collection) => Array<[key, value]>

Returns an array of entries (e.g. [key, value] tuples) of the value. Immutable.js has no function that does this, they have entries() which returns an iterator, and entrySeq() which returns an Immutable.js Seq.

Functions - Side effects
forEach
Description coming soon
Functions - Creating subsets
slice
Description coming soon
rest
Description coming soon
butLast
butLast() => (collection) => newCollection

Returns a new collection of the same type containing all entries except the last.

skip
Description coming soon
skipLast
Description coming soon
skipUntil
Description coming soon
skipWhile
Description coming soon
take
Description coming soon
takeLast
Description coming soon
takeUntil
Description coming soon
takeWhile
Description coming soon
Functions - Reducing a value
reduce
Description coming soon
reduceRight
Description coming soon
every
Description coming soon
some
Description coming soon
join
Description coming soon
isEmpty
isEmpty() => (collection) => boolean

Returns true when the collection is empty, such as an empty object or an array with no elements.

isNotEmpty
isNotEmpty() => (collection) => boolean

Returns true when the collection is not empty.

count
count() => (collection) => number

Returns the number of keys on the collection.

size
size() => (collection) => number

Returns the number of keys on the collection. Immutable.js has this as a getter on their collections, Unmutable.js offers this as a function.

startsWith
startsWith(otherCollection) => (collection) => boolean

Returns true when the collection starts with otherCollection. The elements of otherCollection are compared deeply with those in collection.

endsWith
endsWith(otherCollection) => (collection) => boolean

Returns true when the collection starts with otherCollection. The elements of otherCollection are compared deeply with those in collection.

Functions - Cloning
clone
clone() => (collection) => newCollection

Returns a clone of collection if collection is an array or object, or returns the collection unchanged if given an Immutable.js Map or List. Immutable.js data types are inherently immutable so do not need to be explicitly cloned.

replaceEqual
replaceEqual(otherCollection) => (collection) => newCollection

If otherCollection is deeply equal to collection, otherCollection is returned, or else collection is returned.

This can be useful if you have a data source that always recreates a data structure, such as JSON.parse(), but you want to avoid needlessly passing new instances of unchanged objects and arrays downstream.

replaceEqualDeep
replaceEqualDeep(otherCollection) => (collection) => newCollection

If otherCollection is deeply equal to collection, otherCollection is returned. If not, each of collections children are compared against otherCollection, and if they are deeply equal then that part of otherCollection is inserted into the result. This process continues recursively down the data structure.

This can be useful if you have a data source that always recreates a data structure, such as JSON.parse(), but you want to avoid needlessly passing new instances of unchanged objects and arrays downstream.

Functions - Debugging
log
log(message: string = '', type: string = 'log') => (value) => value

Returns an evaluator that passes the value through unchanged, but also calls console[type](message, value). Useful for debugging.

Utils
Utils include functions that make Unmutable.js useable and useful, as well as plain-Javascript friendly versions of some of Immutable.js top level functions.
Utils - Composition
compose
compose(...functions: Array<Function>) => (value) => newValue

Composes (combines) functions together from right to left. Returns a function that's ready to accept a value and run it through all the functions in the pipe.

composeWith
composeWith(value, ...functions: Array<Function>) => (value) => newValue

Accepts an value as the first argument, and composes (combines) functions in the remaining arguments together from right to left.

pipe
pipe(...functions: Array<Function>) => (value) => newValue

Composes (combines) functions together from left to right. Returns a function that's ready to accept a value and run it through all the functions in the pipe.

pipeIf
pipeIf(condition: (value) => boolean, ...functions: Array<Function>) => (value) => newValue

Like pipe(), but the first argument is a conditional function that is passed the value. If a truthy value is returned from the conditional function, all functions in the pipe are executed. If a falsey value is returned, then the remaining functions in the pipe are skipped.

pipeWith
pipeWith(value, ...functions: Array<Function>) => (value) => newValue

Accepts an value as the first argument, and composes (combines) functions in the remaining arguments together from left to right.

identity
identity() => (value) => value

A function that passes values through with no change. Useful for readable code.

Utils - Predicates
isAssociative
isAssociative(value: any) => boolean

Works like Immutable.js isAssociative() but also identifies plain Javascript arrays and objects as being associative.

isCollection
isCollection(value: any) => boolean

Works like Immutable.js isCollection() but also identifies plain Javascript arrays and objects as being collections.

isImmutable
isImmutable(value: any) => boolean

Returns true if value is an Immutable.js data type, or false otherwise.

isIndexed
isIndexed(value: any) => boolean

Works like Immutable.js isIndexed() but also identifies plain Javascript arrays as being indexed.

isKeyed
isKeyed(value: any) => boolean

Works like Immutable.js isKeyed() but also identifies plain Javascript objects as being keyed.

isObject
isObject(value: any) => boolean

Tests if something extends from object and is not primitive, which includes arrays, functions, class instances and all Immutable.js types, and does not include undefined, null, string, number, and boolean.

isOrdered
isOrdered(value: any) => boolean

Works like Immutable.js isOrdered() but also identifies plain Javascript arrays as being ordered.

isPlainObject
isPlainObject(value: any) => boolean

Tests if the value is a plain object according to is-plain-object.

isRecord
isRecord(value: any) => boolean
isValueObject
isValueObject(value: any) => boolean

An alias for isObject() to align with Immutable.js naming convention.

isWriteable
isWriteable(value: any) => boolean

Tests if a data type can be used with unmutable functions that write or modify data. Returns true for any Immutable.js types, array and plain objects.

Utils - Method creation
method

A helper function that allows you to turn any method into a point-free function.

overload
overload({[arity: string]: Function})

Simulates function overloading in Javascript. Pass it an object with functions as values. The objects keys should be strings of numbers that indicate how many arguments each function expects to receive.

Utils - Conditionals
doIf
doIf( predicate: (value) => boolean, ifTrue: (value) => newValue, ifFalse: (value) => newValue = ii => ii ) => (value) => newValue

Passes the value to the predicate function. If the predicate returns true, the value is then passed through the ifTrue function and the result is returned. If the predicate returns false then the value is simply returned unchanged.

If the third argument ifFalse is provided, then the value will be passed through ifFalse when the predicate returns false.

Utils - Dataset generation
range
range([start = 0], end, [step = 1])

Helper function to generate an array of sequential numbers. Simply a re-export of lodash.range

Inspiration

Immutable.js has a wonderfully symmetrical and well thought out API, and it deals with data immutably. Its a fantastic library, but its drawbacks are that it's large in size and impossible to take only the parts of the API you want, which makes it quite unsuitable for small Javascript libraries. It also requires you use its special data types which can involve a lot of to-ing and fro-ing between normal Javascript types and Immutable.js types.

Ramdajs and Lodash/fp both allow for functions to be composed together point-free style, which is a really nice way of programming, and it naturally keeps the bundle size small as you only import what you need. Lodash/fp's main drawback is that its API is large, unwieldy and unpredictable. Ramdajs' API is better planned, but it's overall pretty esoteric and alien to programmers who are not used to fully functional languages. And both of those libraries use currying, which unmutable deliberately avoids*.

Unmutable deliberately avoids currying because currying in Javascript requires functions to have fixed arity, and Unmutable cannot have fixed arity functions because it must match Immutable.js which uses optional arguments / variable arity functions.

Development

Unmutable is written and maintained by Damien Clarke, with feedback from others at 92green. All online library discussion happens over on Github.

I hope this library helps solve some Javascript data manipulation problems for you. 🎉