Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Объекты первого класса 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;

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

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

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

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

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

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

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

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

Об обучении на Хекслете

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

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

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

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

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

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

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

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

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

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

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

Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»