Русский
Русский
English
Статистика
Реклама

Перевод Compose повсюду композиция функций в JavaScript

Перевод статьи подготовлен специально для студентов курса JavaScript Developer.Basic.





Введение


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

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

Основы


Мы рассмотрим много функций Lodash, потому что 1) мы не собираемся писать собственные базовые алгоритмы это отвлечет нас от того, на чем я предлагаю сконцентрироваться; и 2) библиотека Lodash используется многими разработчиками, и ее можно без проблем заменить на Underscore, любую другую библиотеку или ваши собственные алгоритмы.

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

var compose = function(f, g) {    return function(x) {        return f(g(x));    };};


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

А теперь рассмотрим этот код:

function reverseAndUpper(str) {  var reversed = reverse(str);  return upperCase(reversed);}


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

var reverseAndUpper = compose(upperCase, reverse);


Теперь можно использовать функцию reverseAndUpper:

reverseAndUpper('тест'); // ТСЕТ


Мы могли бы получить такой же результат, написав код:

function reverseAndUpper(str) {  return upperCase(reverse(str));}


Этот вариант выглядит более элегантным, его проще сопровождать и использовать повторно.

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

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

var compose = function() {  var funcs = Array.prototype.slice.call(аргументы);   return funcs.reduce(function(f,g) {    return function() {      return f(g.apply(this, аргументы));    };  });};


С такой функцией мы можем написать примерно такой код:

Var doSometing = compose(upperCase, reverse, doSomethingInitial); doSomething('foo', 'bar');


Существует много библиотек, в которых реализована функция compose. Наша функция compose нужна нам, чтобы понять, как она работает. Конечно, в разных библиотеках она реализована по-своему. Функции compose из разных библиотек решают одну и ту же задачу, поэтому они взаимозаменяемы. Теперь, когда нам понятно, в чем суть функции compose, давайте на следующих примерах рассмотрим функцию _.compose из библиотеки Lodash.

Примеры


Начнем с простого:

function notEmpty(str) {    return ! _.isEmpty(str);}


Функция notEmpty это отрицание значения, возвращаемого функцией _.isEmpty.

Мы можем добиться такого же результата с использованием функции _.compose из библиотеки Lodash. Напишем функцию not:

function not(x) { return !x; } var notEmpty = _.compose(not, _.isEmpty);


Теперь можно использовать функцию notEmpty с любым аргументом:

notEmpty('foo'); // truenotEmpty(''); // falsenotEmpty(); // falsenotEmpty(null); // false


Это очень простой пример. Давайте рассмотрим что-нибудь посложнее:
функция findMaxForCollection возвращает максимальное значение из коллекции объектов со свойствами id и val (значение).

function findMaxForCollection(data) {    var items = _.pluck(data, 'val');    return Math.max.apply(null, items);} var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}]; findMaxForCollection(data);


Для решения этой задачи можно использовать функцию compose:

var findMaxForCollection = _.compose(function(xs) { return Math.max.apply(null, xs); }, _.pluck); var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}]; findMaxForCollection(data, 'val'); // 6


Здесь есть над чем поработать.

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

function pluck(key) {    return function(collection) {        return _.pluck(collection, key);    }}


Функцию findMaxForCollection нужно еще немного подкрутить. Давайте создадим собственную функцию max.

function max(xs) {    return Math.max.apply(null, xs);}


Теперь можно сделать функцию compose более элегантной:

var findMaxForCollection = _.compose(max, pluck('val')); findMaxForCollection(data);


Мы написали собственную функцию pluck и можем использовать ее только со свойством 'val'. Возможно, вам непонятно, зачем писать собственный метод выборки, если в Lodash уже есть готовая и удобная функция _.pluck. Проблема в том, что _.pluck ожидает коллекцию в качестве первого аргумента, а мы хотим сделать по-другому. Изменив порядок следования аргументов, мы можем применить функцию частично, передав ключ в качестве первого аргумента; возвращаемая функция будет принимать данные (data).
Можно еще немного подшлифовать наш метод выборки. В Lodash есть удобный метод _.curry, который позволяет записать нашу функцию так:

function plucked(key, collection) {    return _.pluck(collection, key);} var pluck = _.curry(plucked);


Мы просто оборачиваем первоначальную функцию pluck, чтобы поменять местами аргументы. Теперь pluck будет возвращать функцию до тех пор, пока не обработает все аргументы. Посмотрим, как выглядит окончательный код:

function max(xs) {    return Math.max.apply(null, xs);} function plucked(key, collection) {    return _.pluck(collection, key);} var pluck = _.curry(plucked); var findMaxForCollection = _.compose(max, pluck('val')); var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}]; findMaxForCollection(data); // 6


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

var findMaxForCollection = _.compose(max, pluck('val'));


Такой код проще сопровождать, его можно использовать многократно, и выглядит он куда более элегантно. Рассмотрим последний пример просто для того, чтобы еще раз насладиться элегантностью композиции функций.
Возьмем данные из предыдущего примера и добавим новое свойство active (активный). Сейчас данные выглядят так:

var data = [{id: 1, val: 5, active: true},             {id: 2, val: 6, active: false },             {id: 3, val: 2, active: true }];


Назовем эту функцию getMaxIdForActiveItems(data). Она принимает коллекцию объектов, отфильтровывает все активные объекты и возвращает максимальное значение из отфильтрованных.

function getMaxIdForActiveItems(data) {    var filtered = _.filter(data, function(item) {        return item.active === true;    });     var items = _.pluck(filtered, 'val');    return Math.max.apply(null, items);}


А можно сделать этот код поэлегантнее? В нем уже есть функции max и pluck, поэтому нам остается лишь добавить фильтр:

var getMaxIdForActiveItems = _.compose(max, pluck('val'), _.filter); getMaxIdForActiveItems(data, function(item) {return item.active === true; }); // 5


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

function filter(fn) {    return function(arr) {        return arr.filter(fn);    };}


Добавим функцию isActive, которая принимает объект и проверяет, присвоено ли флагу active значение true.

function isActive(item) {    return item.active === true;}


Функцию filter с функцией isActive можно применить частично, поэтому в функцию getMaxIdForActiveItems мы будем передавать только данные.

var getMaxIdForActiveItems = _.compose(max, pluck('val'), filter(isActive));


Теперь нам нужно лишь передать данные:

getMaxIdForActiveItems(data); // 5


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

var isNotActive = _.compose(not, isActive); var getMaxIdForNonActiveItems = _.compose(max, pluck('val'), filter(isNotActive));


Заключение


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

Ссылки


lodash
Hey Underscore, You're Doing It Wrong! (Эй, Underscore, ты все делаешь не так!)
@sharifsbeat



Читать ещё:


Источник: habr.com
К списку статей
Опубликовано: 25.09.2020 18:15:34
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании otus. онлайн-образование

Javascript

Программирование

Compose

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru