Ramda Thinking: Persistence and Arrays
- Transfer
- Tutorial
1. First steps
2. We combine functions
3. Partial application (currying)
4. Declarative programming
5. Pointless notation
6. Fixedness and objects
7. Fixedness 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, pickand has. Ramda has even more methods for reading array elements.
The equivalent propfor an array is nth ; the equivalent for pickis slice , and the equivalent for hasis it 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) // => trueSlice 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) // => 60init(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 functions assoc, dissocand omitto add, update and delete properties.
Since arrays have an ordered data structure, we have several methods that perform the same work as assocfor 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, appendAnd prependadd 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 method merge. 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]) // => [10, 20, 30, 40, 50, 60, 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 c 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 pipelines.
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) // => [10, 20, 60]
without([30, 40, 50], numbers) // => [10, 20, 60]
drop(3, numbers) // => [40, 50, 60]
dropLast(3, numbers) // => [10, 20, 30]Notice that it removetakes an index and a quantity, while it slicetakes 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]
// умножим третий элемент массива на 10
update(2, multiply(10, nth(2, numbers)), numbers) // => [10, 20, 300, 40, 50, 60]To simplify this typical case, Ramda provides an adjust method that works very similarly evolveto objects. But unlike evolve, it adjustworks with only one element of the array.
const numbers = [10, 20, 30, 40, 50, 60]
// умножим третий элемент массива на 10
adjust(multiply(10), 2, numbers)Note that the first two arguments to adjustgo 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 a change for yourself adjust(multiply(10))and subsequently 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.