Как создать функцию, которая частично применяет обобщения другой функции

#typescript

#машинописный текст #typescript

Вопрос:

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

Примерный случай:

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

 declare function sayHello<A, B, C>(arg1: A, arg2: (arg: B) => C, /* multiple complex args... */) : C[]
  

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

 const sayNumericalHello<X> = sayHello<X,number, number>
  

Чтобы ее можно было использовать повторно только с одним обобщением:

 sayNumericalHello<string>(/* args... */)
sayNumericalHello<number>(/* args... */)
sayNumericalHello<boolean>(/* args... */)
  

Возможно ли это? Я довольно новичок в typescript, так что извините, если это элементарно, но я нигде не смог найти подобного «наследования функций».

Комментарии:

1. Нет, типы более высокого порядка не существуют в TS

Ответ №1:

Вы можете добиться аналогичного результата, указав значения по умолчанию для переменных типа B и C. Поскольку вы говорите, что в большинстве случаев вам нужны значения по умолчанию, это может сработать для вас.

 declare function sayHello<A, B = number, C = number>(arg1: A, arg2: (arg: B) => C, /* multiple complex args... */) : C[];
  

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

 sayHello('test', (a) => a); //string
sayHello(123, (a) => a); // number
sayHello(true, (a) => a); // boolean
  

Другой вариант — определить функцию только с одним параметром типа, который вызывает другую, более универсальную функцию, это немного менее эффективно, поскольку вы выполняете другой вызов функции только для того, чтобы получить нужную подпись. Например:

 function sayNumericalHello<A>(arg1: A, arg2: (arg: number) => number, /* multiple complex args... */) : number[] {
    return sayHello(arg1, arg2, /* multiple complex args... */)
}
  

Комментарии:

1. Можно ли выполнить первое решение, если функция из пакета npm?

2. Можете ли вы предоставить больше информации о пакете? Это пакет, который, я думаю, создан не вами.

3. Мой конкретный вариант использования — это попытка расширить @reduxjs/toolkit ‘s createAsyncThunk . У меня есть несколько случаев, когда я использую в основном одни и те же параметры, но всего несколько разных (например, ожидал string[], (the generic type:) T[], MyAppState ). С точки зрения «работы» копировать и вставлять не так уж много, но для стандартизации, а не дублирования кода это было бы большой победой, я думаю.

4. Я посмотрел на определение типа для этой библиотеки, и оно очень сложное, поэтому, на мой взгляд, лучше всего, если вы возьмете мое второе решение и объявите свою собственную функцию, которая вызывает другую функцию. Можно изменять типизацию с помощью расширения модуля, но в данном случае я не думаю, что это практично, потому что в определении функции createAsyncThunk также используется много внутренних типов. Вам придется дублировать все, а не только определение функции, поэтому каждый раз, когда что-то в библиотеке меняется, будет много работы.

5. Спасибо за помощь. Вы определенно правы в том, что второе решение является лучшим маршрутом!

Ответ №2:

Одним из решений было бы использовать каррирование:

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

Таким образом, универсальный тип может быть добавлен к каждому уровню функции соответствующим образом.

на примере :

 declare function sayHello<A, B, C>(arg1: A, arg2: (arg: B) => C, /* multiple complex args... */) : C[]
  

может быть преобразован как

 declare function sayHello<A>(arg1: A) : <B, C>(arg2: (arg: B) => C, /* complex args... */) => C[]
  

использование становится

 sayHello("")((arg: number) => {})