[JavaScript, Программирование] Compose повсюду: композиция функций в JavaScript (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Перевод статьи подготовлен специально для студентов курса «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'); // 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
оригинал
===========
Источник:
habr.com
===========
===========
Автор оригинала: А. Шариф
===========Похожие новости:
- [Читальный зал, Программирование, Управление персоналом] Вредные советы: Как обучить джуниора
- [3D-принтеры, C, Программирование микроконтроллеров] Прошивка для фотополимерного LCD 3D-принтера своими руками. Часть 2
- [Laravel, Программирование] Новинки Laravel 8
- [Java, Программирование] Знакомимся с Event Sourcing. Часть 2 (перевод)
- [Программирование, Разработка мобильных приложений, GitHub] Используем бесплатные возможности Github Actions для CI/CD на Flutter-проекте
- [PHP, Программирование] Собеседование php-developer в 2020
- [C++, Программирование] Полиморфные аллокаторы c++17
- [Python, Node.JS, Машинное обучение] Machine learning in browser: ways to cook up a model
- [Программирование, Тестирование веб-сервисов] Враг не пройдёт, или как помочь командам соблюдать стандарты разработки
- [MongoDB, NoSQL, Администрирование баз данных] 14 вещей, которые я хотел бы знать перед началом работы с MongoDB (перевод)
Теги для поиска: #_javascript, #_programmirovanie (Программирование), #_javascript, #_compose, #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
), #_javascript, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:13
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Перевод статьи подготовлен специально для студентов курса «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'); // 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 оригинал =========== Источник: habr.com =========== =========== Автор оригинала: А. Шариф ===========Похожие новости:
Блог компании OTUS. Онлайн-образование ), #_javascript, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:13
Часовой пояс: UTC + 5