Мышление в стиле Ramda: первые шаги

Автор: admin от 13-02-2018, 08:35, посмотрело: 34

Недавно я познакомился с замечательной серией статей "Thinking in Ramda", которые проясняют на простых и ясных примерах способы написания кода в функциональном стиле с использованием библиотеки Ramda. Эти статьи показались мне настолько прекрасными, что я не смог удержаться от того, чтобы не перевести их на русский язык. Надеюсь, что в этом будет польза для многих людей :) Давайте начнём перевод с первой вступительной статьи.



1. Первые шаги

2. Сочетаем функции

3. Частичное применение (каррирование)

4. Декларативное программирование

5. Бесточечная нотация

6. Неизменяемость и объекты

7. Неизменяемость и массивы

8. Линзы

9. Заключение



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



Я буду использовать библиотеку Ramda в этих статьях, хотя многие из обсуждаемых идей применимы также к множеству других библиотек, таких как Underscore и Lodash, а также к другим языкам программирования.



Я буду придерживаться лёгкой, менее академической стороны функционального программирования. Это в основном потому что я хочу, чтобы серия была доступна большему числу людей, но также частично и потому что я сам не так близок к истинно функциональной дороге.Используем Ramda с Redux" (я надеюсь также перевести и эту статью впоследствии — прим. пер.), я показал некоторые примеры того, как Ramda может быть использована в различных контекстах при написании Redux приложения.



— В "Используем Redux-api-middleware с Rails", я использовал Ramda для трансформации полезной нагрузки к запросам и возвращаемым ответам.



Я нашёл Ramda прекрасно спроектированной библиотекой, которая предоставляет множество инструментов для чистого и элегантного функционального программирования в javascript.



Если вы желаете поэкспериментировать с Ramda в процессе чтения этой серии статей, то на сайте Ramda имеется удобная браузерная песочница для ваших экспериментов.



Функции



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



Это простая функция, написанная на javascript:



function double(x) {
  return x * 2
}


Вместе со стрелочными функциями из ES6, вы можете написать ту же самую функцию гораздо более кратко. Я упоминаю об этом сейчас, потому что мы будем использовать множество стрелочных функций по мере продвижения наших статей.



const double = x => x * 2


Некоторые языки идут дальше и предоставляют поддержку для функций как конструкций первого класса. Под «конструкциями первого класса» я подразумеваю, что функции могут использоваться таким же образом, как прочие значения. К примеру, вы можете:



— ссылаться на них в константах и переменных

— передавать их в качестве параметров в другие функции

— возвращать их как результат от других функций



javascript — один из подобных языков, и мы будем использовать это преимущество.



Чистые функции



При написании функциональных программ, вы в конце концов приходите к пониманию важности работы с так называемыми «чистыми» функциями.



Чистые функции — это функции, которые не имеют побочных эффектов. Они ничего не присваивают внешним переменным, они не уничтожают входные данные, не генерируют вывод, не читают и не пишут в базу данных, они не изменяют параметры, которые были им переданы, и так далее.



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



Безусловно, вы можете делать различные дела с нечистыми функциями (и должны, если ваша программа делает что-то интересное), но для большей части кода вы желаете сохранить свои функции чистыми.



Неизменяемость



(или «иммутабельность», как часто выражаются фп'шники — прим. пер.)



Другая важная концепция в функциональном программировании — это «иммутабельность». Что это значит? «Иммутабельный» означает «неизменяемый».



Когда я работаю c иммутабельностью, после первичной инициализации значения или объекта, я уже не изменяю их вновь. Это значит, что вы не изменяете элементы в массиве или свойства объектов.



Если мне необходимо изменить что-то в массиве или объекте, — я возвращаю новую его копию с изменёнными значениями. В последующих постах мы поговорим об этом в подробностях.



Иммутабельность идёт рука об руку с чистыми функциями. Поскольку чистые функции не имеют права создавать побочные эффекты, они не имеют права изменять внешние структуры данных. Они вынуждены работать с данными в иммутабельном стиле.



С чего начать?



Самый простой путь начать мыслить в функциональной парадигме — начать заменять циклы на итерационные функции.



Если вы пришли с другого языка, который имеет эти функции (Ruby и Smalltalk лишь два примера), вы можете быть уже знакомы с ними.



Мартин Флауер имеет набор прекрасных статей о «Потоках коллекций», которые показывают, как использовать эти функции и как отрефакторить существующий код в потоки обработки коллекций.



Обратите внимание, что все эти функции (за исключением reject) доступны в Array.prototype, так что вам не нужна Ramda для того чтобы начать использовать их. Тем не менее, я буду использовать Ramda версии для согласованности с остальными статьями.



forEach



Вместо того чтобы писать явный цикл, попробуйте использовать функцию forEach вместо этого. Вот так:



// Замените это:
for (const value of myArray) {
  console.log(value)
}
 
// на это:
forEach(value => console.log(value), myArray)


forEach берёт функцию и массив, и вызывает эту функцию к каждому элементу массива.



В то время как forEach — это наиболее доступная из этих функций, она используется в наименьшей степени при выполнении функционального программирования. Она не возвращает значения, так что она в реальности используется только для вызова функций, которые имеют побочные эффекты.



map



Следующая наиболее важная функция, которую мы изучим — это map. Как и forEach, map применяет функцию к каждому элементу массива. Тем не менее, в отличии от forEach, map собирает результат применения это функции в новый массив и возвращает его.



Вот вам пример:



map(x => x * 2, [1, 2, 3])  // - [2, 4, 6]


Он использует анонимную функцию, но мы можем использовать здесь и именованную функцию:



const double = x => x * 2
 
map(double, [1, 2, 3])




filter/reject



Теперь, давайте взглянем на filter и reject. Как следует из названия, filter выбирает элементы из массива, на основе некоторой функции. Вот пример:



const isEven = x => x % 2 === 0
 
filter(isEven, [1, 2, 3, 4])  // - [2, 4]


filter применяет эту функцию (isEven в данном случае) к каждому элементу массива. Всякий раз, когда функция возвращает «правдивое» значение, соответствующий элемент включается в результат. И также всякий раз, когда функция возвращает «ложное» значение, соответствующий элемент исключается (фильтруется) из массива.



reject делает точно такую же вещь, но в обратном смысле. Она сохраняет элемент для каждой функции, которая вернёт ложное значение, и исключает элемент для тех функций, которые вернут истинное значение.



reject(isEven, [1, 2, 3, 4]) // - [1, 3]


find



find применяет функцию к каждому элементу массива и возвращает первый элемент, для которого функция возвращает истинное значение.



find(isEven, [1, 2, 3, 4]) // - 2




reduce



reduce это немного более сложная чем другие функции, которые мы сегодня рассмотрели. Это стоит знать, но если у вас проблемы с пониманием сути её работы, не позволяйте этому останавливать вас. Вы можете пройти довольно долгий путь даже не понимая суть её работы.



reduce принимает функцию с двумя аргументами, изначальное значение и массив для работы с ним.



Первый аргумент, который будет передан функции, называется «аккумулятором», а вторым аргументом является значение итерируемого массива. Функция должна вернуть новое значение «аккумулятора».



Давайте взглянем на пример и затем разберём то, что в нём происходит:



const add = (accum, value) => accum + value
 
reduce(add, 5, [1, 2, 3, 4]) // - 15



  • reduce вызывает функцию (add) с изначальным значением (5) на первом элементе массива (1). add возвращает новое значение аккумулятора (5 + 1 = 6).

  • reduce снова вызывает add, это время нового значения аккумулятора (6), и следующего значения массива (2). add возвращает 8.

  • reduce вызывает add снова с 8 и следующим значением (3), результат получается 11.

  • reduce вызывает add в последний раз с 11 и последним значением массива (4), результатом является 15.

  • reduce возвращает конечное аккумулируемое значение в качестве результата (15)





  • Заключение



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



    В следующей серии



    Следующий пост в этой серии, «Сочетаем функции», покажет, как мы можем перейти к следующему шагу и начать совмещать функции в новых интересных вариантах.

    Источник: Хабрахабр

    Теги: ramda fp j, vascript

    Категория: Операционные системы » Linux

    Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
    Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
    Введите два слова, показанных на изображении: *