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.
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.
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.
For combining two objects into one, we learned about merge
. Ramda provides concat
for doing the same with arrays.
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.
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.
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.
Note that the first two arguments to adjust
are swapped when compared with update
. This can be a source of confusion, but makes sense when you consider partial application. You might want to provide an adjustment function with adjust(multiply(10))
and then later decide which index and array to apply that adjustment to.
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.