Почему Typescript не поддерживает правильную перегрузку функций? - PullRequest
0 голосов
/ 17 ноября 2018

Существует множество вопросов о том, как работает перегрузка функций в Typescript (например, Перегрузка функций TypeScript ). Но нет таких вопросов, как «почему это работает таким образом?» Теперь перегрузка функции выглядит так:

function foo(param1: number): void; 
function foo(param1: number, param2: string): void;

function foo(...args: any[]): void {
  if (args.length === 1 && typeof args[0] === 'number') {
    // implementation 1
  } else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string') {
    // implementation 2
  } else {
    // error: unknown signature
  }
}

Я имею в виду, Typescript был создан, чтобы облегчить жизнь программиста, добавив так называемый «синтаксический сахар», который дает преимущества OOD. Так почему же Typescript не может делать эти надоедливые вещи вместо программиста? Например, это может выглядеть так:

function foo(param1: number): void { 
  // implementation 1 
}; 
function foo(param1: number, param2: string): void {
  // implementation 2 
};
foo(someNumber); // result 1
foo(someNumber, someString); // result 2
foo(someNumber, someNumber); // ts compiler error

И этот код Typescript будет перенесен в следующий код Javascript:

function foo_1(param1) { 
  // implementation 1 
};
function foo_2(param1, param2) { 
  // implementation 2 
}; 
function foo(args) {
  if (args.length === 1 && typeof args[0] === 'number') {
    foo_1(args);
  } else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string') {
    foo_2(args);
  } else {
    throw new Error('Invalid signature');
  }
};

И я не нашел никакой причины, почему Typescript не работает так. Есть идеи?

Ответы [ 2 ]

0 голосов
/ 18 ноября 2018

Это интересное упражнение, чтобы подумать о том, как бы вы реализовали «настоящие» перегрузки функций в TypeScript, если бы захотели.Достаточно просто заставить компилятор взять кучу отдельных функций и сделать из них одну функцию.Но во время выполнения эта единственная функция должна знать, какую из нескольких базовых функций вызывать, основываясь на количестве и типах аргументов.Количество аргументов определенно может быть определено во время выполнения, но типы аргументов полностью стерты , поэтому реализовать это невозможно, и вы застряли.100

Конечно, вы можете нарушить одну из целей разработки TypeScript (в частности, не цель # 5 о добавлении информации о типе среды выполнения), но этого не произойдет.Может показаться очевидным, что, когда вы проверяете number, вы можете вывести typeof xxx === 'number', но что вы будете выводить при проверке пользовательского interface?Один из способов справиться с этим - попросить разработчика предоставить для каждой перегрузки функции определяемый пользователем тип защиты , который определяет, являются ли аргументы правильными типами.Но теперь дело в том, чтобы разработчики определяли пары вещей для каждой перегрузки функции, что сложнее, чем текущая концепция перегрузки TypeScript.

Для интереса, давайте посмотрим, насколько близко вы можете к этому подойти.как библиотека, которая ожидает функции-и-тип-охранники для создания перегруженной функции.Примерно так (в предположении TS 3.1 или выше):

interface FunctionAndGuard<A extends any[]=any[], R=any, A2 extends any[]= A> {
  function: (...args: A) => R,
  argumentsGuard: (args: any[]) => args is A2
};
type AsAcceptableFunctionsAndGuards<F extends FunctionAndGuard[]> = { [K in keyof F]:
  F[K] extends FunctionAndGuard<infer A, infer R, infer A2> ?
  FunctionAndGuard<A2, R, A> : never
}
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type Lookup<T, K> = K extends keyof T ? T[K] : never;
type FunctionAndGuardsToOverload<F extends FunctionAndGuard[]> =
  Lookup<UnionToIntersection<F[number]>, 'function'>;

function makeOverloads<F extends FunctionAndGuard[]>(
  ...functionsAndGuards: F & AsAcceptableFunctionsAndGuards<F>
): FunctionAndGuardsToOverload<F> {
  return ((...args: any[]) =>
    functionsAndGuards.find(fg => fg.argumentsGuard(args))!.function(...args)) as any;
}

Функция makeOverloads() принимает переменное число аргументов FunctionAndGuard и возвращает одну перегруженную функцию.И попробуйте:

function foo_1(param1: number): void {
  // implementation 1 
};
function foo_2(param1: number, param2: string): void {
  // implementation 2 
};

const foo = makeOverloads({
  function: foo_1,
  argumentsGuard: (args: any[]): args is [number] =>
    args.length === 1 && typeof args[0] === 'number'
}, {
    function: foo_2,
    argumentsGuard: (args: any[]): args is [number, string] =>
      args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string'
  }
);

foo(1); // okay
foo(1, "two"); // okay
foo(1, 2); // error

Работает.Ура?

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

Надежда, которая дает некоторое понимание.Удачи!

0 голосов
/ 17 ноября 2018

Typescript был создан, чтобы облегчить жизнь программиста, добавив так называемый «синтаксический сахар», который дает преимущества OOD.

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

...