Pull to refresh

Мышление в стиле Ramda: Неизменяемость и массивы

Reading time4 min
Views4K
Original author: Randy Coulman

1. Первые шаги
2. Сочетаем функции
3. Частичное применение (каррирование)
4. Декларативное программирование
5. Бесточечная нотация
6. Неизменяемость и объекты
7. Неизменяемость и массивы
8. Линзы
9. Заключение


Данный пост — это седьмая часть серии статей о функциональном программировании под названием "Мышление в стиле Ramda".


В шестой части мы говорили о работе с объектами JavaScript в функциональном и иммутабельном стиле.


В данном посте мы поговорим о подобной работе с массивами.


Чтение элементов массива


В шестой части мы узнали о различных функциях Ramda, предназначенных для чтения свойств объектов, таких как prop, pick и has. Ramda имеет ещё больше методов для чтения элементов массивов.


Эквивалент prop для массива — это nth; эквивалент для pick — это slice, и эквивалент для has — это contains. Давайте взглянем на них.


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 берёт два индекса и возвращает подмассив, который начинается на первом индексе (начиная с нуля) и включает все элементы до второго индекса, но не включая элемент этого индекса.


Получение доступа к первому и последнему элементам массива довольно распространено, так что Ramda предоставляет короткие функции для этих случаев, head и last. Она также предоставляет функции для получения всех элементов, кроме первого (tail), всех, кроме последнего (init), первых N элементов (take(N)), и последних N элементов (takeLast(N)). Давайте взглянем на них в действии.


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]

Добавляем, обновляем и удаляем элементы массива


Изучая работу с объектами, мы узнали о функциях assoc, dissoc и omit для добавления, обновления и удаления свойств.


Так как массивы имеют упорядоченную структуру данных, у нас есть несколько методов, которые выполняют ту же работу, что и assoc для объектов. Наиболее распространённые — это insert и update, но Ramda также предоставляет методы append и prepend для типичных случаев добавления элементов в начало и конец массива. insert, append, и prepend добавляют новые элементы в массив; update "заменяет" определённый элемент в массиве новым значением.


Как вы можете ожидать от функциональной библиотеки, все эти функции возвращают новый массив с ожидаемыми изменениями; оригинальный же массив никогда не изменяется.


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]

Для объединения двух объектов в один, мы ранее узнали о методе merge. Ramda также предоставляет метод concat для выполнения той же операции с массивами.


const numbers = [10, 20, 30, 40, 50, 60]

concat(numbers, [70, 80, 90]) // => [10, 20, 30, 40, 50, 60, 70, 80, 90]

Обратите внимание, что второй массив присоединился к первому. Это выглядит логичным при использовании этого метода в отдельности от другого кода, но, также как и с merge, эта логика может привести не совсем к тому, что мы хотели бы ожидать, если мы будем использовать этот метод в нашем конвеере. Я нашёл полезным для себя написание функции-помощника, concatAfter: const concatAfter = flip(concat), чтобы использовать её в своих конвеерах.


Ramda также предоставляет несколько возможностей для удаления элементов. remove удаляет элементы по их индексу, в то время как without удаляет их по их значению. Также есть такие методы как drop и dropLast для типичных случаев, когда мы удаляем элементы из начала или конца массива.


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]

Обратите внимание, что remove принимает индекс и количество, в то время как slice принимает два индекса. Эта неконсистентность может сбить с толку, если вы не будете знать об этом.


Преобразование элементов


Также как и с объектами, мы можем пожелать обновить элемент массива, применив функцию к оригинальному значению.


const numbers = [10, 20, 30, 40, 50, 60]

// умножим третий элемент массива на 10
update(2, multiply(10, nth(2, numbers)), numbers) // => [10, 20, 300, 40, 50, 60]

Чтобы упростить этот типичный случай, Ramda предоставляет метод adjust, который работает подобно evolve для объектов. Но в отличии от evolve, adjust работает только с одним элементом массива.


const numbers = [10, 20, 30, 40, 50, 60]

// умножим третий элемент массива на 10
adjust(multiply(10), 2, numbers)

Обратите внимание, что первые два аргумента к adjust идут наоборот, если сравнивать их с update. Это может быть источником ошибок, но оно имеет смысл тогда, когда вы будете рассматривать частичное применение. Вы можете пожелать сделать для себя функцию изменения adjust(multiply(10)) и впоследствии решить, какой именно индекс массива изменять с помощью неё.


Заключение


Теперь у нас есть инструменты для работы с массивами и объектами в декларативном и иммутабельном стиле. Это позволяет нам строить программы, состоящие из маленьких, функциональных строительных блоков, совмещая функции, которые будут делать то, что нам нужно, и всё это без изменения всех наших структур данных.


Далее


Мы изучили способы чтения, обновления и трансформации свойств объектов и элементов массивов. Ramda предоставляет и другие основные инструменты для выполнения данных операций, линзы. Следующая статья о линзах должна будет показать нам, как они работают.

Tags:
Hubs:
Total votes 10: ↑10 and ↓0+10
Comments0

Articles