¿Es posible crear un nuevo tipo de función a partir de una función mecanografiada existente que tenga los mismos parámetros y genéricos pero sin el tipo de retorno?

Ejemplo:

Estoy intentando crear el tipo ReturnNothing a partir de la función ReturnSame sin tener que definirlo manualmente.

type ReturnSame = <T extends string>(a: T) => T
type ReturnNothing = <T extends string>(a: T) => void

Intentos:

function returnSame<T extends string>(a: T): T {
  return a;
}

// Attempt 1: Does not work. Generics is not following to new type.
const returnNothing1 = (...params: Parameters<typeof returnSame>) => {
  console.log(params);
};

// Attempt 2: Does not work. Return type is forced.
const returnNothing2: typeof returnSame = (...params) => {
  console.log(params);
};

// Attempt 3: Does not work. OmitReturn is a made up Typescript utility.
const returnNothing3: OmitReturn<typeof returnSame> = (...params) => {
  console.log(params);
};

// Usage
const same = returnSame<"a">("a");
const nothing1 = returnNothing1<"b">("b");

1
Henning Hall 23 feb. 2021 a las 00:48

2 respuestas

La mejor respuesta

Actualmente no hay forma de hacer esto automáticamente puramente en el nivel de tipo. Significado: no puede tomar un tipo de función genérico como ReturnSame y tomar sus parámetros de una manera que no conecte inmediatamente unknown (o cualquiera que sea su restricción genérica) para su parámetro de tipo (s ) como T. Hacer esto probablemente requeriría tipos de kinded superiores que TypeScript no admite actualmente directamente (consulte microsoft / TypeScript # 1213 para la solicitud de función).

Usted podría darse por vencido por completo y escribir returnNothing() manualmente como una función genérica, pero no quiere hacer eso.

Si es aceptable bajar al nivel de valor, pasando returnSame() a una función, puede aprovechar inferencia de tipo de orden superior a partir de funciones genéricas como se introdujo en TypeScript 3.4:

function makeNothingReturner<A extends any[]>(fn: (...a: A) => any): (...a: A) => void {
  return (...a) => void fn(...a);
}

const returnNothing = makeNothingReturner(returnSame);

Aquí makeNothingReturner() toma una función (posiblemente genérica) y devuelve otra función con los mismos parámetros con un tipo de retorno void. La firma de tipo para este se ve muy similar a lo que estaba haciendo en su código de nivel de tipo, por lo que puede ser sorprendente que no haga lo mismo y que returnNothing sea de tipo (...args: unknown) => void. Pero he aquí, IntelliSense muestra esto:

// const returnNothing: <T extends string>(a: T) => void

Y, por lo tanto, puede usarlo como desee, llamando con un parámetro de tipo especificado:

const nothing = returnNothing<"b">("b"); // works

Enlace del área de juegos al código

3
jcalz 22 feb. 2021 a las 22:04

Además de lo que dijo @jcalz: es parcialmente posible a nivel de tipo. (infantil enlace)

type GenFN<S, T> = <AS extends S>(a: AS) => T


type VoidFN<G extends GenFN<any, any>> =
  G extends GenFN<infer S, any>
    ? GenFN<S, void>
    : never


type ThisWorks = VoidFN<GenFN<1, 2>>


type ThisDoesNotWork = VoidFN<<AS extends 1>(a: AS) => 2>

Pero esto requeriría que uses el asistente GenFN en todas partes en lugar de definiciones directas como <AS extends 1>(a: AS) => 2, lo que hace que VoidFN sea incompatible con partes de tu base de código (actual y futura) y bibliotecas de terceros.

Otro riesgo: no estoy seguro de si TS podría "olvidar" / "descartar" la información que un tipo específico extiende GenFN debido a optimizaciones internas en caso de que su base de código se vuelva grande y las definiciones de tipo estén profundamente anidadas.

0
Garlef Wegart 23 feb. 2021 a las 05:49