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

Параметрический полиморфизм JS: Полиморфизм

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

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

В библиотеке lodash есть функция _.concat(), которая объединяет переданные ей массивы:

_.concat([1], [2, 3, 1]); // [1, 2, 3, 1]
_.concat(['one'], ['two', 'three']); // ['one', 'two', 'three']
_.concat([true], [false, false, true]); // [true, false, false, true]

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

// Это немного урезанная версия функции concat, она работает только с двумя аргументами,
// каждый из которых — массив
// Функция создает новый массив, затем обходит по очереди переданные массивы
// и добавляет их значения во вновь созданный массив. Затем он возвращается наружу.
const concat = (coll1, coll2) => {
  const result = [];
  coll1.forEach((value) => result.push(value));
  coll2.forEach((value) => result.push(value));
  return result;
};

Посмотрите внимательно на этот код. Выполняются ли в нем какие-либо операции над данными внутри массива? Правильный ответ — нет. Эти данные перекладываются из одного массива в другой, но над ними не происходит никаких действий. Наша новая функция concat(), так же как и исходная _.concat(), может работать с массивами, содержащими любые типы данных.

Для разработчиков, которые писали только на динамических языках, такое поведение кажется естественным, но в статических языках не все так просто.

const ages = [34, 18, 6, 60, 25];
const words = ['one', 'two', 'three'];

В TypeScript, при создании массива, задается тип элементов этого массива. Для первого это number, для второго string. Добавление в массив элемента другого типа приведет к ошибке:

// В массив чисел добавляется строка
ages.push('hello'); // Error

Функции, обрабатывающие массивы, ожидают определенный тип:


const concat = (arr1: number[], arr2: number[]): number[] => {
  // Создаем результирующий массив, длина которого равна сумме длин исходных массивов
  const result: number[] = [];

  // Переносим в result все значения из первого массива
  for (let i = 0; i < arr1.length; i += 1) {
    result[i] = arr1[i];
  }

  // Переносим в result все значения из второго массива
  for (let j = 0; j < arr2.length; j += 1) {
    result[arr1.length + j] = arr2[j];
  }

  return result;
};

// Объявление массива a
const a = [1, 2, 3, 4];
// Объявление массива b
const b = [4, 16, 1, 2, 3, 22];

// Сливаем массивы
const result = concat(a, b);

console.log(result); // => [1, 2, 3, 4, 4, 16, 1, 2, 3, 22]

Обратите внимание на сигнатуру функции concat(): (arr1: number[], arr2: number[]): number[]. В отличие от варианта на JavaScript здесь указано, что входными параметрами являются массивы чисел number[]. То есть для массива строк эта функция работать не будет. Не будет она работать и для всех остальных типов данных.

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

Именно тут нам пригодится параметрический полиморфизм. Статическим языкам приходится вводить в язык специальные конструкции, которые позволяют описывать подобные алгоритмы безотносительно типа параметра. В некоторых языках их называют шаблонами (C++) или дженериками (TypeScript, Java, C#):

const concat = <T>(arr1: T[], arr2: T[]): T[] => {
  const result: T[] = [];

  for (let i = 0; i < arr1.length; i += 1) {
    result[i] = arr1[i];
  }

  for (let j = 0; j < arr2.length; j += 1) {
    result[arr1.length + j] = arr2[j];
  }

  return result;
};

const a = [1, 2, 3, 4];
const b = [4, 16, 1, 2, 3, 22];

const result = concat(a, b);

// Вызов функции с типом number[]
console.log(result); // => [ 1, 2, 3, 4,  4, 16, 1, 2, 3, 22 ]

const words1 = ['one', 'two', 'three'];
const words2 = ['four', 'five'];

// Вызов функции с типом string[]
const wordsResult = concat(words1, words2);

console.log(wordsResult); // => [ 'one', 'two', 'three', 'four', 'five' ]

В этом коде появляется тип T, что как раз и означает возможность использования с любым типом внутри массива. Теперь метод concat() работает подобно аналогу из JavaScript.

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

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

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


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

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

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

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

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля

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

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

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

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