#7 Converge в Ramda

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

В этом видео мы с вами рассмотрим такой метод Ramda, как converge.

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

Давайте напишем 2 массива

const isValidAr = [6, 3, 4, 5, 2]
const isInvalidAr = [3, 4, 6, 1]

console.log(isFirstElementBiggest(isValidAr))
console.log(isFirstElementBiggest(isInvalidAr))

Соответственно, при вызове нашей функции на первый массив, она должна вернуть true, а на второй false.

На чистом javascript мы могли бы написать функцию вот так

const isFirstElementBiggest = elements =>
  elements[0] === elements.sort((a, b) => b - a)[0]

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

Если мы посмотрим в браузер, то все работает. Как же мы можем улучшить этот код? Обратите внимание, что мы дважды делаем операции с elements. Если мы делаем какие-то операции с одной и той же переменной несколько раз, чтобы получить результат, то это верный признак того, что мы можем применить converge из Ramda.

В чем идея converge? Мы можем вызвать несколько разных функций на одни и те же данные, и потом применить какую-то функцию на результат этих функций.

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

Давайте попробуем.

const isFirstElementBiggest = R.converge(R.equals, [

])

Converge конечно же каррируется, так что мы можем убрать аргумент. Первым аргументом converge мы передаем R.equals. Это та функция, которая будет вызвана на результаты массива функций.

Первой функцией у нас будет нахождение первого элемента

const isFirstElementBiggest = R.converge(R.equals, [
  elements => elements[0]
])

А второй функцией сортировка и нахождение первого элемента

const isFirstElementBiggest = R.converge(R.equals, [
  elements => elements[0],
  elements => elements.sort((a, b) => b - a)[0]
])

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

Мы можем улучшить этот код еще немного, ведь в Ramda уже есть функция, чтобы найти первый элемент. Это функция head.

const isFirstElementBiggest = R.converge(R.equals, [
  R.head,
  elements => elements.sort((a, b) => b - a)[0]
])

И мы можем написать head во второй функции тоже

const isFirstElementBiggest = R.converge(R.equals, [
  R.head,
  elements => R.head(elements.sort((a, b) => b - a))
])

Теперь мы можем применить сортировку по убыванию из Ramda.

const isFirstElementBiggest = R.converge(R.equals, [
  R.head,
  elements => R.head(R.sort(R.descend(R.identity))(elements))
])

Итак:

  1. Мы вызываем sort
  2. Указываем descend для сортировки по убыванию
  3. Передаем в descend identity, для того, чтобы указать по какому признаку мы хотим сортировать. identity - просто оборачивает переменную в функцию, которая возвращает эту переменную.

Этот код стало достаточно сложно читать из-за большой композиции. Мы можем, с помощью метода compose, сделать этот код читабельнее и он будет point free.

const isFirstElementBiggest = R.converge(R.equals, [
  R.head,
  R.compose(R.head, R.sort(R.descend(R.identity)))
])

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

const sortByBiggestFirst = R.sort(R.descend(R.identity))
const isFirstElementBiggest = R.converge(R.equals, [
  R.head,
  R.compose(R.head, sortByBiggestFirst)
])

Итак еще раз:

  1. converge принимает первым параметром функцию, в которую, как аргументы передадутся результаты всех функций из массива
  2. В массиве мы описываем функции, которые хотим применить на аргумент converge.
  3. В нашем случае мы применяем на массив функцию R.head, чтобы найти первый элемент и функцию композиции, которая сортирует массив и находит первый элемент
  4. На результат этих двух функций мы вызываем R.equals.

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

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
fil
1год назад назад
Добрый день. хотелось написать функцию фильтрации ключей одного объекта на основании данных другого: filterWithKeys = R.pipe( R.toPairs, R.filter(([key, val]) => R.has(key, obj2)), R.fromPairs) let a = filterWithKeys(obj); как сделать чтобы вызов выглядел так filterWithKeys(obj1)(obj2) - ? Заранее спасибо за вашу работу и замечательные уроки.
monsterlessons
1год назад назад
Никак, потому что у вас filterWithKeys - это pipe. И в нем первая функция - это R.toPairs, которая принимает только один аргумент. Вы можете только передать аргументы в функцию. const filterWithKeys = R.curry((obj1, obj2) => { return R.pipe( R.toPairs, R.filter(([key, val]) => R.has(key, obj2)), R.fromPairs )(obj1) }) Тогда вы можете вызывать filterWithKeys({a: 1}, {b: 2, a: 2}). Кстати вы можете написать по другому, чтобы было меньше кода. const obj1 = {a: 1, b: 2} const obj2 = {b: 3} const keys = R.useWith(R.intersection, [R.keys, R.keys])(obj1, obj2) const filteredObj = R.pick(keys, obj1)
fil
1год назад назад
Добрый день. Как можно менять параметры местами. Например если описываешь методы класса то данные часто постоянны. т.е. приходится менять параметры местами. Есть стандартная техника вытаскивания параметров наружу когда много вложенных функций?
monsterlessons
1год назад назад
Добрый день. Да конечно. Обычно для этого есть 2 подхода. 1. С помощью placeholder: Заключается в том, что мы оставляем место для значение которое пробрасывается автоматически. Очень удобно в R.compose. R.compose( R.lt(R.__, 10) )(5) В этом случае мы хотим, чтобы 5 стало на место placeholder и тогда R.lt вернет true. Второй вариант с помощью R.flip вы можете переставлять аргументы местами R.compose( R.flip(R.lt)(10), )(5)
Alex Panchuk
1год назад назад
Привет. Не совсем понял, зачем нужно использовать R.identity и почему без нее не работает? Спасибо за хороший материал.
monsterlessons
1год назад назад
R.identity просто оборачивает переменную в функцию. Это нужно тогда, когда метод принимает функцию, а не переменную.
Александр Анплеенко
1год назад назад
Добрый день, очень нравятся ваши уроки по рамде, очень помогают в проекте, особенно метод pathOr, он просто шикарен. В связи с чем у меня появился такой кейс, есть 2 формы данные из них берутся из стора, и создаются они redux-form, и проблема состоит в том что надо из этих двух форм собрать данные в один js обьект и отправить на сервер, и еще при том чтобы если каких то полей нет, то их и не должно быть в результате, обьединить 2 обьекта не проблема, но как сделать чтобы удалять поля, ума не приложу как сделать красиво, есть ли в рамде подобные методы?
monsterlessons
1год назад назад
Добрый день. Спасибо. Самое простое, это удалить все нулевые поля из обьекта false/null. Обычно в библиотеках, это метод compact. В Ramda из коробки его нет, но можно легко написать. const compact = R.filter(R.identity) compact({id: 1, name: null, isAdmin: false})