Главная Категории Контакты Поиск

Частичное применение или partial application в JavaScript

Что такое частичное применение в JavaScript и как его использовать.

JavaScriptFunctional programming·13.06.2019·читать 3 мин 🤓·Автор: Alexey Myzgin

Частичное применение c замыканием (closure)

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

В JavaScript мы можем использовать области видимости способом, который позволит нам предоставить состояние функции или объекту, не подвергая это состояние непосредственно внешнему миру. Вот этот процесс и называется замыканием. Например:

function сallCount(fn) {
  let count = 0

  return (...args) => {
    console.log(`This function has been called ${count++} times`)
    fn(...args)
  }
}

const add = (x, y) => x + y
const addCount = сallCount(add)

addCount(2, 4) // This function has been called 1 times
addCount(7, 5) // This function has been called 2 times
addCount(2, 4) // This function has been called 3 times

Функция сallCount создает переменную count, которая ограничена областью действия тела функции. Мы не можем получить к ней доступ извне этой функции. Однако, анонимная функция, которую мы возвращаем, использует count; так что значение останется доступным для возвращенной функции, пока она существует. Таким образом, новая функция addCount способна выводить состояние count при каждом вызове.

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

Для лучшего понимания частичного применения, советую ознакомится с карри-функциями.

Аргументы, передаваемые функции карри, позволяют нам хранить данные в закрытом виде (closure) для их повторного использования в наших приложениях. Поскольку каждый аргумент, кроме последнего, возвращает новую функцию, мы можем легко создавать повторно-используемые функции, предварительно предоставив некоторые аргументы.

Создаём карри-функцию для извлечения запросов из API, которая использует частичное применение (partial application) для создания функциональности многократного использования. Функция getFromAPI получит baseURL, endpoint и функцию обратного вызова cb, которая выполнится как только мы получим наши данные из запроса.

Используем fetch для получения данных. Наш URL будет комбинацией baseURL и endpoint. Далее превратим наш ответ в JSON. После этого вызываем обратный вызов cb, передав наши данные data, и “ловим” через catch любые ошибки, если такие возникнут.

const getFromAPI = baseURL => endpoint => cb;
fetch(`${baseURL}${endpoint}`)
  .then(res => res.json())
  .then(data => cb(data))
  .catch(err => console.error(err.message));

А сейчас давай частично применим baseURL. Одним из общедоступных API, который мы можем использовать, является GitHub API. У нас есть частично-применённая функция getGitHub, в которую мы можем передавать разные endpoint. Создаем функции для запроса пользователей и репозиторий.

const getFromAPI = baseURL => endpoint => cb;
fetch(`${baseURL}${endpoint}`)
  .then(res => res.json())
  .then(data => cb(data))
  .catch(err => {
    console.error(err.message);
  });

const getGithub = getFromAPI("https://api.github.com");

const getGithubUsers = getGithub("/users"); // https://api.github.com/users
const getGithubRepos = getGithub("/repositories"); // https://api.github.com/repositories

Теперь у нас есть две новые функции, каждая из которых частично применила один и тот же baseURL, но, так же частично применила и разные endpoint. Последнее, что мы можем передать - это обратный вызов cb, который вызовет fetch. Давай используем функцию getGithubUsers и передадим различные обратные вызовы для каждого из них.

В первом вызове выведем в консоль user.login имена. Основным преимуществом частичного применения является наша способность откладывать оценку функции, в то же время предоставляя некоторые аргументы для хранения и повторного использования этой функции в нашем приложении. Во втором вызове выведем в консоль user.avatar_url.

const getFromAPI = baseURL => endpoint => cb =>
  fetch(`${baseURL}${endpoint}`)
    .then(res => res.json())
    .then(data => cb(data))
    .catch(err => {
      console.error(err.message);
    });

const getGithub = getFromAPI("https://api.github.com");

const getGithubUsers = getGithub("/users"); // https://api.github.com/users
const getGithubRepos = getGithub("/repositories"); // https://api.github.com/repositories

getGithubUsers(data => {
  console.log('login', data.map(user => user.login));
});

getGithubUsers(data => {
  console.log('avatar_url', data.map(user => user.avatar_url));
});

В качестве результата мы получили 2 массива login и avatar_url, которые содержат запрашиваемые нами данные.

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

Частичное применение можно осуществить и без карри.

Метод bind функции позволяет нам предоставлять аргументы функции, которые применяются к новой возвращаемой функции, документация здесь.

Первый аргумент bind - это thisArg. Этот аргумент будет связан с this для новой функции. Мы передадим null, потому как не хотим, чтобы что-либо связывалось с this.

Любой аргумент, передаваемый в bind после thisArg, частично применяется как аргумент к функции. Мы можем передать любое количество аргументов, и если передадим меньше, чем ожидает функция, получим частично-применённую функцию.

const multiply = (x, y) => x * y

const multiply2 = multiply.bind(null, 2)

console.log(multiply2(3)) // 6
console.log(multiply2(4)) // 8
console.log(multiply2(5)) // 10

Заключение

Частичное применение - это просто естественная комбинация каррированных функций и замыканий. Ранее примененные аргументы сохраняются и доступны для возвращаемых новых функций. Этот процесс может существенно облегчить написание и понимание кода.

Website, name & logo
Copyright © 2022. Alex Myzgin