Функция Generi c, которая возвращает значение того же T или другого R, не может принять функцию более высокого порядка '(i: T) => i' в качестве значения параметра по умолчанию - PullRequest
2 голосов
/ 31 марта 2020

Я хотел бы понять, как получить, где я хочу, с помощью generi c type, так что это мой пример:

function foo(value: 42 = 42 ,bar:(val: any) => any = I => I){
return bar(value)
}

Я работаю с value некоторого типа T в моем Например, типом является number, поэтому я использую 42 Я хочу иметь функцию в качестве аргумента, которая изменит значение на другой тип R или вернет тот же тип T, который в этом случае будет идентификатор «I => I» в качестве значения параметра по умолчанию.

Я переписал свою функцию в виде функции стрелки следующим образом:

const foo = <R1>(
  value: number = 42,
  bar: <R0>(val: number) => R0 = I => I
) => {
  return bar<R1>(value);
};

const foo0 = (
  value: number = 42,
  bar: <R0>(val: number) => R0 = I => I
) => {
  return bar(value);
};

const foo1 = <R1>(
  value: number = 42,
  bar: (val: number) => R1 = I => I
) => {
  return bar(value);
};


ERROR: Type 'number' is not assignable to type 'RX'. 'RX' could be instantiated with an arbitrary type which could be unrelated to 'number'.

I я использую R1 и R0, потому что я не уверен, что разница между R one и R ничем в этом примере, но если я удаляю значение I => I по умолчанию, сообщение об ошибке go исчезнет, ​​но я не понимаю, почему или как это преодолеть ...

в приведенном ниже примере fooB(42,i=>i) и foo0B(42,i=>i) выветрились, но не foo1B(42,i=>i)

Как установить значение по умолчанию I => I без необходимости указывать оба R | number

const fooB = <R1>(value: number = 42, bar: <R0>(val: number) => R0) => {
  return bar<R1>(value);
};

const foo0B = (value: number = 42, bar: <R0>(val: number) => R0) => {
  return bar(value);
};

const foo1B = <R1>(value: number = 42, bar: (val: number) => R1) => {
  return bar(value);
};


fooB(42,i=>i)
foo0B(42,i=>i)
foo1B(42,i=>i)

1 Ответ

2 голосов
/ 31 марта 2020

Сообщение об ошибке, которое вы получаете из своих примеров, вызвано тем фактом, что аргументы типа могут быть явно указаны при вызове функции, например:

fooB<number>(42, (i) => i);

с учетом fooB:

const fooB = <R1>(value: number = 42, bar: <R0>(val: number) => R0) => {
    return bar<R1>(value);
};

, что здесь происходит, так это то, что внутренне bar можно вызывать с любым другим типом, кроме R1. Если это так, то нет гарантии, что number (тип value) является типом, совместимым с R0.

Проблема возникает, когда мы делаем, например:

fooB<21>(42, (i) => i);

Об этом вызове мы можем сказать: R1 создается с / as 21.

Здесь 42 extends number, как предполагается с учетом типа value, но number extends 21 ЛОЖЬ. Другими словами: 42 и 21 - это разные подтипы типа number. Поэтому возвращаемое значение i не доступно ни для R1, ни R0. Аналогичная проблема возникает с foo0B и R0.

Уже есть хорошее покрытие о том, почему появляется это сообщение об ошибке.


Исправление

Если я правильно понимаю, вам нужна функция, которая:

  • Принимает аргумент типа T
  • Опционально принимает функцию fn, которая принимает аргумент введите T и возвращает значение типа U
  • Вызывает fn с первым аргументом, а затем возвращает результат (U), если задано fn, или возвращает T в противном случае (идентичность).

Самый простой способ написать это примерно так:

function foo<T, U>(value: T, fn: (val: T) => T | U = i => i): T | U {
    return fn(value);
}

Но здесь и foo, и fn действительно могут возвращать только тип объединения T | U, поскольку для обоих случаев имеется только одна подпись

  • без fn
  • с fn

Если вы хотите ветвление типов в зависимости от того, передан ли обратный вызов (я предполагаю, что это по умолчанию I => I) вместо этого вы можете использовать перегрузку функции . По сути, это все равно, что сказать «У меня есть следующие 2 подписи для foo»:

function foo<T>(value: T): T;
function foo<T, U>(value: T, fn: (val: T) => U): U;
function foo<T>(value: T, fn = (i: T) => i) {
    return fn(value);
}

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

Вторая сигнатура принимает обратный вызов fn. Если аргументы типа явно не даны для вызова foo, U будет выведен из типа fn. В противном случае тип fn будет проверен на соответствие данному типу для U.

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

Вот полная игровая площадка TS с использованием следующих примеров:

// Without callback
const a: number = foo(42);             // Fine
const b: string = foo("I'm a string"); // Fine
const c: string = foo(42);             // ERROR: type '42' is not assignable to type 'string'

// With callback
const d: number = foo('42', parseInt);                  // Ok
const e: string = foo(42, (x: number) => x.toString()); // Ok
const f: string = foo(42, (x: number) => x + 1);        // ERROR: type 'number' is not assignable to type 'string'

// With explicit types
const g: number = foo<string, number>('42', parseInt);  // Ok
const h: number = foo<string, number>('42', (x) => x);  // ERROR: type 'string' is not assignable to type 'number'
...