Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Частичное применение

Частичное применение функций — техника, основанная на возможности возвращать функции из других функций. Допустим, у нас есть функция sum, суммирующая три числа.

const sum = (a, b, c) => a + b + c;
sum(4, 3, 1); // 8
sum(1, 1, 1); // 3

Частичное применение позволяет на основе старой функции создать новую, которая "частично применена". Для начала вспомним, что такое применение функции. Математики никогда не говорят, что функция вызывается с некоторыми аргументами — вместо этого они говорят, что функция была применена к этим аргументам. В примере выше функция sum была применена к трем аргументам 4, 3 и 1. Такое применение можно назвать полным, то есть в функцию было передано столько аргументов сколько и ожидалось. Здесь возникает вопрос, а можно было по-другому? Да, можно.

Частичное применение, техника которую проще всего объяснить в отрыве от языков программирования, на языке близком к математике (для простоты назову его Ha). Представьте, что наша функция имеет вот такое определение:

-- Слева имя функции, затем идет список параметров,
-- разделённых пробелами, и после знака "=>" — тело функции
sum a b c => a + b + c

А её вызов выглядит так:

-- Скобок и запятых нет, но это равносильно sum(4, 3, 1) в js
sum 4 3 1 -- 8
sum 1 1 1 -- 3

Ha — необычный язык. Если внутри него вызвать функцию с неполным набором параметров, то в отличие от js он не вызовет саму функцию (или не упадёт с ошибкой как во многих других языках). Он вернёт новую функцию, которая "частично применена".

-- sum2 новая функция, полученная частичным применением функции sum к числу четыре
-- Применение "частичное", потому что в функцию sum передаётся только один параметр, а не три
sum2 = sum 4
sum2 1 1 -- 6
sum2 3 2 -- 9

Другими словами, такой вызов создаёт новую функцию, которая работает точно так же, как и исходная, но, при этом, часть её аргументов как будто уже подставлены. То есть наш вызов sum2 1 1 в действительности приведёт к вызову sum 4 1 1. Этот трюк работает с любым количеством аргументов. Посмотрите на частичное применение двух аргументов:

sum3 = sum 1 1
-- sum3 принимает только один аргумент
sum3 2 -- 4
sum3 1 -- 3

Арифметика аргументов очень простая. Если исходная функция принимала 3 параметра, то после частичного применения одного параметра, новая функция будет принимать на вход два параметра (2 + 1 = 3), если частично применились два параметра, то новая функция принимает на вход 1 параметр. А можно ли частично применить три параметра для функции, которая принимает на вход 3 параметра? Конечно нет, ведь это будет обычный вызов функции со всеми параметрами. Довольно несложно догадаться как выглядело бы определение функций sum2 и sum3 если бы мы их описали явно:

sum a b c => a + b + c
sum2 b c => 4 + b + c
sum3 c => 1 + 1 + c

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

-- Создаем первую функцию с 3 - 1 аргументами
sum4 = sum 2
-- Создаем вторую функцию с 2 - 1 аргументами
sum5 = sum4 3
-- Вызываем то что получилось. В реальности вычисляется 2 + 3 + 1
sum5 1 -- 6
sum5 4 -- 9

В js эта возможность не встроена прямо в сам язык, но ее можно реализовать с помощью дополнительной функции:

const sum = (a, b, c) => a + b + c;

// Берем исходную функцию sum и отдаем ее в функцию partialApply
const sumWithFour = partialApply(sum, 4);
sumWithFour(3, 1); // 8
sumWithFour(10, 3); // 17

const sumWithFourAndTwo = partialApply(sumWithFour, 2);
sumWithFourAndTwo(3); // 9

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

Вот как может выглядеть реализация partialApply для функций от трёх аргументов (таких как sum):

const partialApply = (fn, arg1) => (arg2, arg3) => fn(arg1, arg2, arg3);

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

const salary1 = getAverageSalary('programmer', 'spain');
const salary2 = getAverageSalary('programmer', 'russia');
const salary3 = getAverageSalary('programmer', 'usa');

Первым параметром функция принимает на вход название профессии, вторым — страну, на выходе — годовая зарплата. Все довольно примитивно, но часть данных дублируется. Вычисляется зарплата программистов в разных странах, что приводит к постоянному вызову функции с первым параметром programmer. В этом нет ничего криминального, но существует способ устранить подобное повторение. Убрать с помощью введения дополнительной константы.

const job = 'programmer'
const salary1 = getAverageSalary(job, 'spain');
const salary2 = getAverageSalary(job, 'russia');
const salary3 = getAverageSalary(job, 'usa');

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

const getProgrammersSalaryByCountry = partialApply(getAverageSalary, 'programmer');

const salary1 = getProgrammersSalaryByCountry('spain');
const salary2 = getProgrammersSalaryByCountry('russia');
const salary3 = getProgrammersSalaryByCountry('usa');

Принцип действия частичного применения функции основан на генерации (в нашем случае с помощью функции partialApply) новой функции, оборачивающей старую, причем так, что она принимает на вход меньшее количество аргументов. Для нашей задачи мы применили один аргумент, но это не обязательно. Применять можно любое число аргументов исходной функции (но, естественно, не все, ведь в таком случае мы получим обычный вызов функции). И хотя пример выше выглядит слегка искусственным, в реальной жизни этот прием применяется часто. Причем нередко частично применяется больше одного аргумента за раз.

Тело функции getProgrammersSalaryByCountry выглядит предсказуемо. Внутри вызывается исходная функция с подставленным в аргумент значением.

const getProgrammersSalaryByCountry = country => getAverageSalary('programmer', country);

А partialApply для функций от двух аргументов реализуется способом очень похожим на реализацию flip.

const partialApply = (fn, arg1) => (arg2) => fn(arg1, arg2);

То есть функция partialApply возвращает (генерирует) функцию, которая внутри себя замыкает два параметра: fn и arg1.

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


<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу нужна профессиональная подписка

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».