#4 Функциональный Javascript с Ramda

poster
В этом видео мы с вами познакомимся с такой библиотекой, как Ramda.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

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

То есть Ramda - это набор функций, которые необходимы нам каждый день и которых так не хватает в javascript.

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

Есть две очень важные вещи в функциональном программировании. Первая - все функции должны быть чистыми (по- английски pure). Это значит, что они не должны иметь сайд-эффектов. То есть они не могут присваивать что-то к переменным, которые находятся вне функции, читать из базы или модифицировать параметры, которые мы в них передали.

Вот простейшая чистая функция

const double = x => x * 2

У нас функция принимает число и умножает его на 2. Основная идея чистых функций в том, что вызывая ее с одним и тем же аргументом снова и снова - мы получим такой же результат.

Давайте посмотрим на пример плохой функции

let a = 2
const double = x => {
  a += 2
  return x * a
}

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

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

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

Вот у меня к проекту подключена Ramda. И, если, у нас есть массив и мы запушим в него новый элемент, то это не мутирует наш исходный массив, а вернет копию с данными.

const ar = [1, 2, 3]

const newAr = R.append(4, ar)

console.log(ar)

console.log(newAr)

Также очень важная особенность Ramda в том, что все ее методы каррированы. Это позволяет сразу писать более гибко код.

Давайте попробуем на простом примере. Допустим, у нас есть массив обьектов и мы хотим найти обьект, в котором id = 2.

const users = [
  {
    id: 1,
    name: 'John'
  },
  {
    id: 2,
    name: 'Alex'
  },
  {
    id: 3,
    name: 'Bill'
  }
]

const alex = users.find(user => user.id === 1)

На Ramda есть метод propEq, который возвращает true или false в зависимости от того, равняется ли поле обьекта указанному значению или нет.

R.propEq('id', 2, {id: 2})

Поэтому мы можем использовать R.propEq внутри find.

const alex = R.find(R.propEq('id', 2), users)

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

const isAlex = R.propEq('id', 2)
const alex = R.find(isAlex, users)

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

Давайте разберем другой пример, где Ramda должна упростить код. Например, у нас есть система голосования и есть несколько хелперов, которые нам помогают проверять пользователей.

const wasBornInCountry = person => person.birthCountry === 'UK'
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => person.age >= 18

const isCitizen = person => wasBornInCountry(person) || wasNaturalized(person)
const isEligibleToVote = person => isOver18(person) && isCitizen(person)

const testUser = {
  age: 20,
  birthCountry: 'UK'
}
console.log(isEligibleToVote(testUser))

С помощью Ramda мы можем упростить этот код. У нас есть функции both и either. both - принимает две функции и возвращает новую функцию, которая возвращает true, если обе функции возвращают true.

Давайте рассмотрим пример, чтобы было понятнее.

const test = R.both(value => value < 2, value => value > 0)
console.log(test(1))

То есть R.both возвращает функцию и мы передаем в нее 1. Этот аргумент попадает в каждую функцию, написанную внутри R.both. Если результат обоих функций true, то R.both возвращает true.

В нашем случае, мы можем изменить isEligibleToVote

const isEligibleToVote = R.both(isOver18, isCitizen)

То есть isEligibleToVote - это функция, которая принимает юзера на вход и передает его в isOver18 и isCitizen. Если обе эти функции вернут true, то isEligibleToVote вернет true.

Если мы посмотрим в браузер, то все по-прежнему работает.

Точно также мы можем использовать функцию R.either. Он работает также как R.both, но возвращает true, если хотя бы одна из функций вернет true.

const wasBornInCountry = person => person.birthCountry === 'UK'
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => person.age >= 18

const isCitizen = R.either(wasBornInCountry, wasNaturalized)
const isEligibleToVote = R.both(isOver18, isCitizen)

const testUser = {
  age: 20,
  birthCountry: 'UK'
}
console.log(isEligibleToVote(testUser))

Все по-прежнему работает.

Также, мы можем переписать wasBornInCountry, ведь мы с вами уже разобрались в методе propEq.

const wasBornInCountry = R.propEq('birthCountry', 'UK')

И из этого вытекает еще одна особенность Ramda. Она позволяет писать point free code. То есть функции без аргументов. Как вы видите, у нас есть 3 функции без аргументов, так как они каррируются. Point free хорош тем, что он не завязывается на конкретные аргументы и может переиспользоваться во многих местах.

Например

const idEquals = R.propEq('id')
const isAlex = idEquals(2, {id: 2, name: 'Alex'})
const isFirstPost = idEquals(1, {id: 1, title: 'My first Post'})

Если у вас возникли какие-то вопросы или комментарии, пишите их прямо под этим видео.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
016
5 месяцев назад
Очень круто. Спасибо. А гвоздь всей этой темы - в лекции №1. Без нее смысл ускользнул бы.
monsterlessons
5 месяцев назад
Спасибо. Я старался, чтобы все поняли фундаментальный смысл, а не только научились вызывать методы.
Aleksandr Polyakh
11 месяцев назад
Доброе время суток, подскажите пожалуйста. Как могу слить три и более массивов в которых лежат объекты, в один массив, с выложенными объектами из всех массивов. const arr1 = [{id: 1, name: 'Bob'}]; const arr2 = [{id: 2, name: 'Alex'}]; const arr3 = [{id: 3, name: 'John'}]; merge не подходит только два сливает. Результат не могу получить вот такой: const allArr = [ {id: 1, name: 'Bob'}, {id: 2, name: 'Alex'}, {id: 3, name: 'John'} ]; Спасибо.
monsterlessons
11 месяцев назад
Добрый вечер. R.concat(ar1, ar2, ar2);
Aleksandr Polyakh
11 месяцев назад
concat тоже не подходит, только два сливает. => [{"id": 1, "name": "Bob"}, {"id": 2, "name": "Alex"}] http://prntscr.com/i2snzl Спасибо.
monsterlessons
11 месяцев назад
Черт. С рамдой не нашел как. Редьюс сработал, но не с массивом обьектов. Вот без рамды. arr3 = [...arr1, ...arr2];
Leonid Kuznecov
1год назад назад
Спасибо за такой качественный контент. Смутило только одно. Почему ты пишешь что Ramda это библиотека которая пропагандирует функциональный стиль а lodash нет. До недавнего времени я повсеместно использовал Ramda, но на новом проекте коллеги перевели на lodash, слишком большой разницы пока не заметил, разве что синтаксис слега изменился и порядок вызова аргументов. Во многих сравнениях между этими библиотеками встречаю только следующие отличие карирование и композиция. Но обе эти библиотеки несут функциональный стиль. Если я в чем то не прав прошу поправить. За ранее спасибо
monsterlessons
1год назад назад
Каррирование и есть главное и основное отличие между Ramda и Lodash. Также, все методы Ramda иммутабельны. Методы lodash не каррированы, поэтому даже наличие метода compose у lodash не несет очень большого смысла. Функциональное программирование - это разработка приложений используя чистые функции, без мутации данных и сайд эффектов. У Lodash даже нет иммутабельного метода, чтобы добавить элемент в массив.
Leonid Kuznecov
1год назад назад
С этим я полностью согласен. А как на счёт lodash/fp ?
Leonid Kuznecov
1год назад назад
И ещё у lodash есть отдельный метод _.curry. А также _.set он ведь immutable
monsterlessons
1год назад назад
Lodash/fp больше похож на Ramda. Вообще то set в Lodash мутабельный и работает только с обьектами. Используйте что вам нравится. Главное, что вы знаете, какие есть альтернативы.
Valerii Kuzivanov
1год назад назад
Привет. А будет ли адекватно писать в функциональном стиле в таких фреймворках как React/Vue ? Вроде не вижу преппятствий делать это, но принесет ли это профит?
monsterlessons
1год назад назад
Привет. Я на React практически весь код в компонентах, редьюсерах и экшен криейторах пишу в функциональном стиле. Профит в том, что количество кода сильно уменьшается и читать проще. P.S. на Angular профита явно будет мало потому что в отличии от React, они идут классами и ООП, а не функциональщиной.
Artem Kh
1год назад назад
спасибо, всегда свежие и актуальные темы, выростает ли наша ценность как разработчиков если писать функционально? думаешь ли что будут все так писать в будущем?
Artem Kh
1год назад назад
если не секрет где работаешь или на себя? просто интересно где о каррироании спрашивают?)
monsterlessons
1год назад назад
Сейчас достаточно много компаний, где Ramda становится дефолтной библиотекой для трансформации данных. Год назад так еще не было. Соответственно в функциональном стиле пишут больше. И если вы прийдете в такую компанию, то вам будет проще. Все так писать не будут никогда. Потом что всегда есть как сторонники функционального подхода, так и противники.
monsterlessons
1год назад назад
Работаю в рандомной компании где платят деньги) О каррировании меня спрашивали на одном из собеседований и просили написать с нуля функцию curry на бумажке.