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

Кажется, библиотеки Lodash и Underscore теперь используются повсюду, и в них до сих пор есть известный нам суперэффективный метод compose.
Давайте поближе познакомимся с функцией compose и разберемся, как она может сделать ваш код более читаемым, простым в сопровождении и элегантным.
Мы рассмотрим много функций Lodash, потому что 1) мы не собираемся писать собственные базовые алгоритмы — это отвлечет нас от того, на чем я предлагаю сконцентрироваться; и 2) библиотека Lodash используется многими разработчиками, и ее можно без проблем заменить на Underscore, любую другую библиотеку или ваши собственные алгоритмы.
Прежде чем рассмотреть несколько простых примеров, давайте вспомним, что именно делает функция compose, и разберемся, как при необходимости реализовать собственную функцию compose.
Это самый базовый вариант реализации. Обратите внимание: функции в аргументах будут выполняться справа налево. То есть сначала выполняется функция, расположенная справа, и ее результат передается в функцию слева от нее.
А теперь рассмотрим этот код:
Функция reverseAndUpper сначала переворачивает заданную строку, а затем переводит ее в верхний регистр. Мы можем переписать этот код, используя базовую функцию compose:
Теперь можно использовать функцию reverseAndUpper:
Мы могли бы получить такой же результат, написав код:
Этот вариант выглядит более элегантным, его проще сопровождать и использовать повторно.
Возможность быстро конструировать функции и создавать конвейеры обработки данных пригодится нам в разных ситуациях, когда нужно на ходу трансформировать данные. А теперь представьте, что вам нужно передать в функцию коллекцию, преобразовать элементы коллекции и вернуть максимальное значение после выполнения всех шагов конвейера или преобразовать заданную строку в логическое значение. Функция compose позволяет объединить несколько функций и таким образом создать более сложную функцию.
Давайте реализуем более гибкую функцию compose, которая может включать любое количество других функций и аргументов. Предыдущая функция compose, которую мы рассмотрели, работает только с двумя функциями и принимает только первый переданный аргумент. Мы можем переписать ее так:
С такой функцией мы можем написать примерно такой код:
Существует много библиотек, в которых реализована функция compose. Наша функция compose нужна нам, чтобы понять, как она работает. Конечно, в разных библиотеках она реализована по-своему. Функции compose из разных библиотек решают одну и ту же задачу, поэтому они взаимозаменяемы. Теперь, когда нам понятно, в чем суть функции compose, давайте на следующих примерах рассмотрим функцию _.compose из библиотеки Lodash.
Начнем с простого:
Функция notEmpty — это отрицание значения, возвращаемого функцией _.isEmpty.
Мы можем добиться такого же результата с использованием функции _.compose из библиотеки Lodash. Напишем функцию not:
Теперь можно использовать функцию notEmpty с любым аргументом:
Это очень простой пример. Давайте рассмотрим что-нибудь посложнее:
функция findMaxForCollection возвращает максимальное значение из коллекции объектов со свойствами id и val (значение).
Для решения этой задачи можно использовать функцию compose:
Здесь есть над чем поработать.
_.pluck ожидает коллекцию в качестве первого аргумента, и функцию обратного вызова в качестве второго. А можно применить функцию _.pluck частично? В этом случае можно использовать каррирование, чтобы изменить порядок следования аргументов.
Функцию findMaxForCollection нужно еще немного подкрутить. Давайте создадим собственную функцию max.
Теперь можно сделать функцию compose более элегантной:
Мы написали собственную функцию pluck и можем использовать ее только со свойством 'val'. Возможно, вам непонятно, зачем писать собственный метод выборки, если в Lodash уже есть готовая и удобная функция
Можно еще немного подшлифовать наш метод выборки. В Lodash есть удобный метод
Мы просто оборачиваем первоначальную функцию pluck, чтобы поменять местами аргументы. Теперь pluck будет возвращать функцию до тех пор, пока не обработает все аргументы. Посмотрим, как выглядит окончательный код:
Функция findMaxForCollection может читаться справа налево, то есть сначала в функцию передается свойство val каждого элемента коллекции, а затем она возвращает максимальное из всех принятых значений.
Такой код проще сопровождать, его можно использовать многократно, и выглядит он куда более элегантно. Рассмотрим последний пример — просто для того, чтобы еще раз насладиться элегантностью композиции функций.
Возьмем данные из предыдущего примера и добавим новое свойство active (активный). Сейчас данные выглядят так:
Назовем эту функцию getMaxIdForActiveItems(data). Она принимает коллекцию объектов, отфильтровывает все активные объекты и возвращает максимальное значение из отфильтрованных.
А можно сделать этот код поэлегантнее? В нем уже есть функции max и pluck, поэтому нам остается лишь добавить фильтр:
С функцией
Добавим функцию
Функцию
Теперь нам нужно лишь передать данные:
Теперь ничто не мешает нам переписать эту функцию так, чтобы она находила максимальное значение среди неактивных объектов и возвращала его:
Как вы могли заметить, композиция функций — это захватывающая возможность, которая делает код элегантным и позволяет использовать его многократно. Главное — применять ее правильно.
lodash
Hey Underscore, You're Doing It Wrong! (Эй, Underscore, ты все делаешь не так!)


Введение
Кажется, библиотеки 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'); // true
notEmpty(''); // false
notEmpty(); // false
notEmpty(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

Читать ещё: