Что такое функция высшего порядка?

Spread the love

Оригинальная статья: Nick Scialli – What is a Higher-Order Function?

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


Определение

По определению, функция высшего порядка – это функция, которая либо принимает функцию в качестве аргумента, либо возвращает функцию.

Если вы не знакомы с обработкой функций как first class objects, вы удивитесь, но это возможно. А так же, чрезвычайно полезно!

Несколько простых примеров

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

Принимаем функцию в качестве аргумента

Давайте создадим относительно бесполезную функцию под названием evaluatesToFive, которая принимает два аргумента: первый аргумент будет числом, а второй аргумент будет функцией. Внутри нашей функции evaluatesToFive мы проверим, возвращает ли функция число пять.

function evaluatesToFive(num, fn) {
  return fn(num) === 5;
}

Мы можем это проверить следующим образом:

function divideByTwo(num) {
  return num / 2;
}

evaluatesToFive(10, divideByTwo);
// true

evaluatesToFive(20, divideByTwo);
// false

Немного бесполезно, но это круто, что мы можем это сделать!

Возвращаем функцию

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

function multiplyBy(num1) {
  return function(num2) {
    return num1 * num2;
  };
}

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

const multiplyByThree = multiplyBy(3);
const multiplyByFive = multiplyBy(5);

multipyByThree(10); // 30

multiplyByFive(10); // 50

Опять же, не супер полезно в его нынешнем виде, но довольно круто, несмотря ни на что.

Более сложный и потенциально полезный пример

Более полезным примером функций высшего порядка будет создание объекта валидатора. Основная идея – это будет функция, которая принимает объект в качестве аргумента, а затем любое количество функций, которые должны вернуть значение true, чтобы объект считался действительным.

В этом примере мы будем обрабатывать объект newUser и пытаться определить, следует ли нам разрешить ему регистрироваться в нашем приложении. Пользователь должен соответствовать следующим критериям:

  • Пользователю должно быть не менее 18 лет
  • Пароль должен содержать не менее 8 символов
  • Пользователь должен согласиться с Условиями обслуживания (Terms of Service)

Идеальный объект newUser будет выглядеть примерно так:

const newUser = {
  age: 24,
  password: 'some long password',
  agreeToTerms: true,
};

Основываясь на этих знаниях, мы можем создать некоторые тестовые функции, которые возвращают значение true, когда наши желаемые условия выполняются, и false в противном случае.

function oldEnough(user) {
  return user.age >= 18;
}

function passwordLongEnough(user) {
  return user.password.length >= 8;
}

function agreeToTerms(user) {
  return user.agreeToTerms === true;
}

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

function validate(obj, ...tests) {
  for (let i = 0; i < tests.length; i++) {
    if (tests[i](obj) === false) {
      return false;
    }
  }
  return true;
}

Рассмотрим что именно здесь происходит:

  1. Мы указываем, что нашим первым аргументом функции является объект (obj). Затем мы используем оператор rest (… tests), чтобы сказать, что любые дополнительные аргументы будут в массиве tests.
  2. Мы используем цикл for, чтобы перебрать наш массив tests, который является массивом функций (это часть функции высшего порядка!).
  3. Мы передаем obj каждому элементу в массиве tests. Если эта функция возвращает false, мы знаем, что obj недействителен и немедленно возвращаем false.
  4. Если мы пройдем весь массив tests без возврата false, наш объект будет действительным, и мы вернем true.

Пример использования

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

const newUser1 = {
  age: 40,
  password: 'tncy4ty49r2mrx',
  agreeToTerms: true,
};

validate(newUser1, oldEnough, passwordLongEnough, agreeToTerms);
// true

const newUser2 = {
  age: 40,
  password: 'short',
  agreeToTerms: true,
};

validate(newUser2, oldEnough, passwordLongEnough, agreeToTerms);
// false

Объект newUser1 правильно считается действительным, а объект newUser2 определяется как недействительный, поскольку его пароль слишком короткий.

Потенциальное улучшение: функция создания валидатора

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

function createValidator(...tests) {
  return function(obj) {
    for (let i = 0; i < tests.length; i++) {
      if (tests[i](obj) === false) {
        return false;
      }
    }
    return true;
  };
}

Давайте посмотрим, как это дает нам более согласованный интерфейс, когда мы снова проверяем наши объекты newUser1 и newUser2:

const userValidator = createValidator(
  oldEnough,
  passwordLongEnough,
  agreeToTerms
);

userValidator(newUser1); // true
userValidator(newUser2); // false

Потрясающие! Теперь вы знаете как используя функцию более высокого порядка createValidator, использовать разные критерии проверки для различных объектов.

Была ли вам полезна эта статья?
[22 / 4.8]

Spread the love
Подписаться
Уведомление о
guest
2 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Катя
3 лет назад

Не знала про функцию first class objects, действительно очень полезная функция, изучу более детально.

Igor
Igor
3 лет назад

Спасибо за заметку.
Мой вариант:
const createValidator = (fn, …tests) => obj => {
for (const test of tests) {
if (test(obj) === false) return;
}
fn(obj)
}

const userValidator = createValidator(
greet = obj => console.log(`hi ${obj?.name}`),

oldEnough,
passwordLongEnough,
agreeToTerms
)