Зарегистрируйтесь, чтобы продолжить обучение

Объекты первого класса JS: Функциональное программирование

Продолжая тему предыдущего урока, познакомимся с понятием "first-class citizen" или "объекты первого класса".

Объектами первого класса в контексте конкретного языка программирования называются элементы, которые могут быть переданы как параметр, возвращены из функции или присвоены переменной. Другими словами, речь идет обо всем, что может быть данными. Самые простые типы данных — это числа и строки. Как вы потом увидите, все остальные типы данных также являются объектами первого класса.

А теперь посмотрите внимательно на этот код: const x = () => console.log('hey'). Ничего необычного, вы видели и писали подобное множество раз. Если вы считаете, что в этом коде создается функция x, на самом деле это не так. Здесь происходят следующие две операции:

  1. Создание функции: () => console.log('hey')
  2. Создание константы со значением в виде функции: const x =

Этот момент нужно хорошо прочувствовать. Минимальное определение функции, которое только возможно, выглядит так: () => {}. Это пустая функция с пустым телом, которая не делает ничего. Присваивать ее константе или нет — вопрос отдельный. Вполне допустимо написать и выполнить подобную программу: (a, b) => a + b;. Попробуйте поэкспериментировать в REPL.

Из примеров выше можно сделать вывод, что функция — тоже данные, ведь ее можно присвоить константе. Рассмотрим это утверждение на практике:

const identity = (v) => v; // функция: (v) => v, константа: identity
console.log(identity(10)); // => 10

const z = identity;
console.log(z === identity); // => true

const x = 5;
console.log(z(x) === identity(x)); // => true

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

Главный вывод, который можно сделать из кода выше, заключается в том, что определение функции — это выражение, а значит оно возвращает значение, а именно — функцию. А раз определение функции — выражение, возвращающее функцию, то мы можем попробовать вызвать ее без создания промежуточной константы:

// Определяем функцию (v) => v и тут же вызываем ее
((v) => v)('run'); // run

// Тот же код с использованием промежуточной константы.
// Попробуйте мысленно заменить `identity` на `(v) => v`, тогда
// получится ((v) => v)('run'). С выражениями так можно поступать всегда.
// const identity = (v) => v;
// identity('run'); // run

Скобки вокруг определения функции — не какой-то особый синтаксис. Здесь они используются с той же целью, что и в арифметических операциях — для группировки. Без них выражение (v) => v('run') приобретает совсем другой смысл. В этом случае в нем создается функция, принимающая на вход другую функцию v и вызывающая ее внутри с аргументом 'run'.

Попробуем усложнить:

identity((v) => v)('run'); // run
// ((v) => v)((v) => v)('run') // run

Первым идет пример вызова функции по идентификатору, а во втором примере мы заменили идентификатор на определение функции, сделав подстановку. Результат получился тот же самый. Еще раз посмотрите на этот шаблон (<тут определение функции>)(). Попробуйте самостоятельно разобрать пример ниже:

((a, b) => a + b)(3, 2); // 5
// const sum = (a, b) => a + b;
// sum(3, 2); // 5

Теперь попробуем использовать функции как данные:

const sqrt = identity(Math.sqrt);
console.log(sqrt === Math.sqrt); // => true
sqrt(4); // 2

В первой строчке вызывается функция identity, в которую передается Math.sqrt. Результатом этого вызова будет все та же функция Math.sqrt.

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

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

const sum = identity((a, b) => a + b);
sum(3, 5); // 8

// const f = (a, b) => a + b;
// const sum = identity(f);
// sum(3, 5); // 8

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

Возникает вопрос: есть ли имя у функций, определенных подобным образом? Имени нет даже у такой функции const f = () => {}. Мы просто связали константу с функцией, но сама функция ничего про константу не знает. Звучит слегка безумно, но это так. Ведь мы можем взять и связать эту функцию уже с другой константой. По этой причине такие функции часто называют анонимными. Другое распространенное название — лямбда-функция. Своим названием лямбда-функция обязана лямбда-исчислению (математическая формальная система, легшая в основу языков семейства lisp). Только в отличие от языков программирования, "лямбды" в лямбда-исчислении — всегда функции от одного аргумента, поэтому общее с функциями из js у них, в первую очередь, анонимность, и то, что они являются объектами первого класса.

Попробуем сделать что-нибудь полезное. Иногда встречается задача, в рамках которой нужно применить одну и ту же функцию несколько раз, например, так: Math.sqrt(Math.sqrt(16)). Создав функцию высшего порядка, можно упростить эту задачу. Рассмотрим пример с двойным применением одноаргументной функции:

const callTwice = (f, arg) => f(f(arg));

callTwice(Math.sqrt, 16); // 2
callTwice((x) => x ** 2, 3); // 81
// const f = (x) => x ** 2;
// f(f(3));

callTwice применяет переданную функцию к своему аргументу два раза. Если расписать подробнее, то происходит следующее:

const res1 = f(arg);
const res2 = f(res1);
return res2;

Аватары экспертов Хекслета

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

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

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

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

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»