Представим, что дженерики пропали из языка. Тогда произойдет дублирование кода. Придется описывать один и тот же алгоритм для разных типов данных много раз.
Возьмем для примера функцию last()
, возвращающую последний элемент массива. Ниже ее обобщенная версия:
function last<T>(coll: T[]): T {
return coll[coll.length - 1];
}
Дженерики также могут использоваться в стрелочных функциях:
const last = <T>(coll: T[]): T => {
return coll[coll.length - 1];
};
Теперь попробуем реализовать то же поведение, но без применения дженериков. Для этого нам придется создать по одной функции для каждого типа. Причем имя функции должно быть уникальным:
function lastForNumberType(coll: number[]): number {
return coll[coll.length - 1];
}
function lastForStringType(coll: string[]): string {
return coll[coll.length - 1];
}
// Тут определения для всех остальных типов
Если типов несколько, то количество функций, которые нужно определить, будет определяться как произведение количества всех возможных типов на количество параметров типа.
Реализация дженерика с помощью перегруженной функции упрощает задачу. Тогда не придется создавать новые имена:
function last(coll: number[]): number;
function last(coll: string[]): string;
// Тут определения для всех остальных типов
function last(coll: any[]): any {
return coll[coll.length - 1];
}
В случае TypeScript даже не будет дублироваться логика, но это особенность именно TypeScript. В других статически типизированных языках придется дублировать и логику.
Какой бы вариант реализации мы не выбрали, соблюдаются две вещи:
В Computer Science свойство функции, позволяющее обрабатывать значения разных типов одним способом (используя один алгоритм), называется параметрическим полиморфизмом. То есть дженерики — это реализация параметрического полиморфизма в TypeScript.
Параметрический полиморфизм играет важную роль в статически типизированных языках там, где приходится явно указывать типы у функций. Он есть почти во всех высокоуровневых статически типизированных языках. В Java и C# это тоже называется дженериками. В C++ используется названия шаблоны, но смысл от этого не меняется, хотя шаблоны в С++ — это больше, чем параметрический полиморфизм.
В противовес статически типизированным языкам в языках с динамической типизацией, таких как JavaScript, Python, Ruby, PHP, дженерики не нужны. В подобных языках любой обобщенный алгоритм автоматически работает для всех типов данных.
Реализуйте описание обощенного типа MyArray
, который представляет аналог массива из JavaScript. Пример использования объекта этого типа:
const coll: MyArray<number> = ...;
coll.push(1); // 1
coll.push(10); // 2
coll.push(99); // 3
const newColl = coll.filter((value) => value % 2 == 0);
console.log(newColl.items); // [10]
Тип включает в себя два метода: push() и filter(), совпадающие по сигнатуре с методами Array. Данные внутри должны храниться в свойстве items
. Для push()
примем соглашение, что метод принимает только один параметр. Игнорируйте остальные параметры.
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1import * as ta from 'type-assertions';
2
3import MyArray from './index';
4
5test('MyArray', () => {
6 const coll: MyArray<number> = {
7 items: [],
8 push(value) {
9 return this.items.push(value);
10 },
11 filter(callback) {
12 const newItems = this.items.filter(callback);
13 return { ...this, items: newItems };
14 },
15 };
16
17 expect(coll.push(1)).toBe(1);
18 expect(coll.push(2)).toBe(2);
19 expect(coll.push(5)).toBe(3);
20
21 ta.assert<ta.Equal<Parameters<MyArray<string>['push']>, [string]>>();
22 ta.assert<ta.Equal<ReturnType<MyArray<string>['push']>, number>>();
23});
24
Решение учителя откроется через: