1. First steps
2. We combine functions
3. Partial application (currying)
4. Declarative programming
5. Ruleless Notation
6. Immutability and objects
7. Immutability and arrays
8. Lenses
9. Conclusion
This post is the seventh part of a series of articles on functional programming called "Thinking in the Ramda Style".
In the sixth part, we talked about working with JavaScript objects in a functional and immutable style.
In this post we will talk about similar work with arrays.
Reading array elements
In the sixth part, we learned about the various functions of Ramda, designed to read the properties of objects, such as prop
, pick
and has
. Ramda has even more methods for reading array elements.
The equivalent prop
for an array is nth ; the equivalent for pick
is slice , and the equivalent for has
is this contains . Let's take a look at them.
const numbers = [10, 20, 30, 40, 50, 60] nth(3, numbers) // => 40 ( ) nth(-2, numbers) // => 50 ( ) slice(2, 5, numbers) // => [30, 40, 50] (. ) contains(20, numbers) // => true
Slice takes two indices and returns a subarray that starts at the first index (starting from zero) and includes all the elements up to the second index, but not including the element of this index.
Getting access to the first and last elements of an array is fairly common, so Ramda provides short functions for these cases, head and last . It also provides functions for getting all elements except the first ( tail ), all but the last ( init ), first N elements ( take (N) ), and the last N elements ( takeLast (N) ). Let's take a look at them in action.
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]
Add, update and delete array elements
Studying work with objects, we learned about the functions of assoc
, dissoc
and omit
to add, update and delete properties.
Since arrays have an ordered data structure, we have several methods that do the same work as assoc
for objects. The most common ones are insert and update , but Ramda also provides append and prepend methods for typical cases of adding elements to the beginning and end of an array. insert
, append
, and prepend
add new elements to the array; update
"replaces" a specific element in the array with a new value.
As you can expect from the functional library, all of these functions return a new array with the expected changes; the original array never changes.
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]
To merge two objects into one, we previously learned about the merge
method. Ramda also provides a concat method for performing the same array operation.
const numbers = [10, 20, 30, 40, 50, 60] concat(numbers, [70, 80, 90])
Note that the second array joined the first one. This seems logical when using this method separately from other code, but, like with merge
, this logic may not quite lead to what we would expect if we use this method in our pipeline. I found it helpful to write the helper function, concatAfter
: const concatAfter = flip(concat)
, to use it in my conveyors.
Ramda also provides several options for removing items. remove removes items by their index, while without removes them by their value. There are also methods such as drop and dropLast for typical cases when we remove elements from the beginning or end of an array.
const numbers = [10, 20, 30, 40, 50, 60] remove(2, 3, numbers)
Note that remove
takes an index and a number, while slice
takes two indices. This inconsistency can be confusing if you do not know about it.
Element conversion
As with objects, we may wish to update the array element by applying the function to the original value.
const numbers = [10, 20, 30, 40, 50, 60]
To simplify this typical case, Ramda provides an adjust method that works very similarly to evolve
objects. But unlike evolve
, adjust
only works with one element of the array.
const numbers = [10, 20, 30, 40, 50, 60]
Notice that the first two arguments to adjust
go the other way around when compared with update
. This may be a source of error, but it makes sense when you consider partial application. You may wish to make changes for yourself adjust(multiply(10))
and then decide which array index to change using it.
Conclusion
Now we have tools for working with arrays and objects in declarative and immutable style. This allows us to build programs consisting of small, functional building blocks, combining functions that will do what we need, and all this without changing all of our data structures.
Further
We studied the ways of reading, updating and transforming the properties of objects and elements of arrays. Ramda provides other basic tools for performing these operations, lenses. The next article on lenses will have to show us how they work.