Ramda Thinking: Persistence and Arrays

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]) // => [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 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) // => [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 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] //      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 to evolve objects. But unlike evolve , adjust only works with one element of the array.


 const numbers = [10, 20, 30, 40, 50, 60] //      10 adjust(multiply(10), 2, numbers) 

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.

Source: https://habr.com/ru/post/415025/


All Articles