Thinking in Ramda: Immutability and Arrays
This post is Part 7 of a series about functional programming called Thinking in Ramda.
In Part 6, we talked about working with JavaScript objects in a functional and immutable way.
In this post, we’ll do the same for arrays.
Reading Array Elements
In Part 6, we learned about various Ramda functions for reading object properties, including prop
, pick
, and has
. Ramda has even more methods for reading array elements.
The array equivalent of prop
is nth
; the equivalent of pick
is slice
, and the equivalent of has
is contains
. Let’s look at those.
Reading Array Elements
const numbers = [10, 20, 30, 40, 50, 60]
nth(3, numbers) // => 40 (0-based indexing)
nth(-2, numbers) // => 50 (negative numbers start from the right)
slice(2, 5, numbers) // => [30, 40, 50] (see below)
contains(20, numbers) // => true
Slice takes two indexes and returns the sub array starting at the first index (0-based) and including all of the elements up to, but not including the second index.
Accessing the first (nth(0)
) and last (nth(-1)
) elements is quite common, so Ramda provides shortcuts for those special cases, head
and last
. It also provides functions for accessing all-but-the-first element (tail
), all-but-the-last element (init
), the first N
elements (take(N)
), and the last N
elements (takeLast(N)
). Let’s see these in action.
More Array Reading
const numbers = [10, 20, 30, 40, 50, 60]
head(numbers) // => 10
tail(numbers) // => [20, 30, 40, 50, 60]
last(numbers) // => 60
init(numbers) // => [10, 20, 30, 40, 50]
take(3, numbers) // => [10, 20, 30]
takeLast(3, numbers) // => [40, 50, 60]
Adding, Updating, and Removing Array Elements
For objects, we learned about assoc
, dissoc
, and omit
for adding, updating, and removing properties.
Because arrays are an ordered data structure, there are several methods that do the same job as assoc
. The most general are insert
and update
, but Ramda also provides append
and prepend
for the common case of adding elements at the beginning or end of the array. insert
, append
, and prepend
add new elements to the array; update
“replaces” an element with a new value.
As you might expect from a functional library, all of these functions return a new array with the desired changes; the original array is never changed.
Adding and Updating Elements
const numbers = [10, 20, 30, 40, 50, 60]
insert(3, 35, numbers) // => [10, 20, 30, 35, 40, 50, 60]
append(70, numbers) // => [10, 20, 30, 40, 50, 60, 70]
prepend(0, numbers) // => [0, 10, 20, 30, 40, 50, 60]
update(1, 15, numbers) // => [10, 15, 30, 40, 50, 60]
For combining two objects into one, we learned about merge
. Ramda provides concat
for doing the same with arrays.
concat
const numbers = [10, 20, 30, 40, 50, 60]
concat(numbers, [70, 80, 90]) // => [10, 20, 30, 40, 50, 60, 70, 80, 90]
Note that the second array is appended to the first. This looks right when used by itself, but as with merge
, it may not do what you expect when used in a pipeline. I find it useful to define a helper function, concatAfter
:
const concatAfter = flip(concat)
for use in pipelines.
Ramda also provides several options for removing elements. remove
removes elements by index, while without
removes them by value. There’s also drop
and dropLast
for the common case of removing elements from the beginning or end of the array.
Removing Elements
const numbers = [10, 20, 30, 40, 50, 60]
remove(2, 3, numbers) // => [10, 20, 60]
without([30, 40, 50], numbers) // => [10, 20, 60]
drop(3, numbers) // => [40, 50, 60]
dropLast(3, numbers) // => [10, 20, 30]
Note that remove
takes an index and a count whereas slice
takes two indexes. This inconsistency can be confusing if you’re not aware of it.
Transforming Elements
As with objects, we may want to update
an array element by applying a function to the original value.
Transforming an Element the Hard Way
const numbers = [10, 20, 30, 40, 50, 60]
update(2, multiply(10, nth(2, numbers)), numbers) // => [10, 20, 300, 40, 50, 60]
To simplify this common use case, Ramda provides adjust
that works much like evolve
does for objects. Unlike evolve
, adjust
only works for a single array element.
Using adjust
const numbers = [10, 20, 30, 40, 50, 60]
adjust(2, multiply(10), numbers)
Conclusion
We now have tools for working with arrays and objects in a declarative and immutable way. This allows us to build programs out of small, functional building blocks, combining functions to do what we need to do, all without mutating our data structures.
Next
We’ve learned ways of reading, updating, and transforming object properties and array elements. Ramda provides a more general tool for performing these operations, the lens. Lenses shows us how they work.