Typescript: Propuesta: Tipos Variadic - Dar tipos específicos a funciones variadic

Creado en 29 oct. 2015  ·  265Comentarios  ·  Fuente: microsoft/TypeScript

Tipos Variadic

Dar tipos específicos a funciones variádicas

Esta propuesta permite a TypeScript dar tipos a funciones de orden superior que toman un número variable de parámetros.
Funciones como esta incluyen concat , apply , curry , compose y casi cualquier decorador que envuelva una función.
En Javascript, se espera que estas funciones de orden superior acepten funciones variadas como argumentos.
Con los estándares ES2015 y ES2017, este uso se volverá aún más común a medida que los programadores comiencen a usar argumentos extendidos y parámetros de descanso tanto para matrices como para objetos.
Esta propuesta aborda estos casos de uso con una única estrategia de tipificación muy general basada en tipos de orden superior.

Esta propuesta abordaría total o parcialmente varias cuestiones, que incluyen:

  1. # 5331 - Tuplas como tipos para resto ... argumentos
  2. # 4130 - El compilador informa incorrectamente la discrepancia de firma de destino de llamada / parámetro cuando se usa el operador de propagación
  3. # 4988 - Las tuplas deben ser clonables con Array.prototype.slice ()
  4. No. 1773 - ¿Genéricos variadic?
  5. # 3870 - Tipos de descanso en genéricos para tipos de intersección.
  6. # 212 - enlazar, llamar y aplicar no tienen tipo (requiere los tipos de esta función de # 3694).
  7. # 1024 - Parámetros de resto escritos con genéricos

Actualizaré esta propuesta en mi bifurcación del TypeScript-Handbook : sandersn /
Tengo una implementación en progreso en sandersn / TypeScript @ f3c327aef22f6251532309ba046874133c32f4c7 que actualmente tiene implementadas las partes simples de la propuesta.
Reemplaza la parte 2 de mi propuesta anterior, # 5296.
Editar: se agregó una sección sobre asignabilidad. Ya no estoy seguro de que sustituya estrictamente al # 5296.

Ejemplo de vista previa con curry

curry para funciones con dos argumentos es fácil de escribir en Javascript y Typecript:

function curry(f, a) {
    return b => f(a, b);
}

y en TypeScript con anotaciones de tipo:

function curry<T, U, V>(f: (t: T, u: U) => V, a:T): (b:U) => V {
    return b => f(a, b);
}

Sin embargo, una versión variadic es fácil de escribir en Javascript pero no se le puede dar un tipo en TypeScript:

function curry(f, ...a) {
    return ...b => f(...a, ...b);
}

A continuación, se muestra un ejemplo del uso de tipos variados de esta propuesta para escribir curry :

function curry<...T,...U,V>(f: (...ts: [...T, ...U]) => V, ...as:...T): (...bs:...U) => V {
    return ...b => f(...a, ...b);
}

La sintaxis para los tipos de tuplas variadic que utilizo aquí coincide con la sintaxis de propagación y descanso utilizada para los valores en Javascript.
Esto es más fácil de aprender, pero puede dificultar la distinción de anotaciones de tipo de expresiones de valor.
De manera similar, la sintaxis para la concatenación parece una construcción de tuplas, aunque en realidad es una concatenación de dos tipos de tuplas.

Ahora veamos un ejemplo de llamada a curry :

function f(n: number, m: number, s: string, c: string): [number, number, string, string] {
    return [n,m,s,c];
}
let [n,m,s,c] = curry(f, 1, 2)('foo', 'x');
let [n,m,s,c] = curry(f, 1, 2, 'foo', 'x')();

En la primera llamada

V = [number, number, string, string]
...T = [number, number]
...U = [string, string]

En la segunda convocatoria,

V = [number, number, string, string]
...T = [number, number, string, string]
...U = []

Sintaxis

La sintaxis de una variable de tipo variable es ...T donde _T_ es un identificador que por convención es una sola letra mayúscula, o T seguida de un identificador PascalCase .
Las variables de tipo variable se pueden utilizar en varios contextos sintácticos:

Los tipos variables se pueden vincular en la ubicación habitual para el enlace de parámetros de tipo, incluidas funciones y clases:

function f<...T,...U>() {}
}
class C<...T> {
}

Y se pueden hacer referencia a ellos en cualquier tipo de ubicación de anotación:

function makeTuple<...T>(ts:...T): ...T {
    return ts;
}
function f<...T,...U>(ts:...T): [...T,...U] {
    // note that U is constrained to [string,string] in this function
    let us: ...U = makeTuple('hello', 'world');
    return [...ts, ...us];
}

Las variables de tipo variable, como las variables de tipo, son bastante opacas.
Tienen una operación, a diferencia de las variables de tipo.
Se pueden concatenar con otros tipos o con tuplas reales.
La sintaxis utilizada para esto es idéntica a la sintaxis de difusión de tuplas, pero en la ubicación de la anotación de tipo:

let t1: [...T,...U] = [...ts,...uProducer<...U>()];
let t2: [...T,string,string,...U,number] = [...ts,'foo','bar',...uProducer<...U>(),12];

Los tipos de tupla son instancias de tipos variados, por lo que continúan apareciendo dondequiera que se permitieran previamente las anotaciones de tipo:

function f<...T>(ts:...T): [...T,string,string] { 
    // note the type of `us` could have been inferred here
    let us: [string,string] = makeTuple('hello', 'world');
    return [...ts, ...us];
}

let tuple: [number, string] = [1,'foo'];
f<[number,string]>(tuple);

Semántica

Una variable de tipo variádica representa un tipo de tupla de cualquier longitud.
Dado que representa un conjunto de tipos, usamos el término "tipo" para referirnos a él, siguiendo su uso en la teoría de tipos.
Debido a que el conjunto de tipos que representa son tuplas de cualquier longitud, calificamos 'kind' con 'variadic'.

Por lo tanto, declarar una variable de tipo tupla variable le permite tomar cualquier tipo de tupla _single_.
Al igual que las variables de tipo, las variables de tipo solo se pueden declarar como parámetros para funciones, clases, etc., lo que luego permite que se utilicen dentro del cuerpo:

function f<...T>(): ...T {
    let a: ...T;
}

Llamar a una función con argumentos escritos como un tipo variadic asignará un tipo de tupla específico al tipo:

f([1,2,"foo"]);

Asigna el tipo de tupla ...T=[number,number,string] ... T . So in this application of f , sea ​​a: ... T is instantiated as sea ​​a: [número, número, cadena] . However, because the type of a is not known when the function is written, the elements of the tuple cannot be referenced in the body of the function. Only creating a new tuple from a` está permitido.
Por ejemplo, se pueden agregar nuevos elementos a la tupla:

function cons<H,...Tail>(head: H, tail: ...Tail): [H,...Tail] {
    return [head, ...tail];
}
let l: [number, string, string, boolean]; 
l = cons(1, cons("foo", ["baz", false]));

Al igual que las variables de tipo, normalmente se pueden inferir las variables de tipo variable.
Las llamadas a cons podrían haberse anotado:

l = cons<number,[string,string,boolean]>(1, cons<string,[string,boolean]>("foo", ["baz", false]));

Por ejemplo, cons debe inferir dos variables, un tipo _H_ y un tipo _... Tail_.
En la llamada más interna, cons("foo", ["baz", false]) , H=string y ...Tail=[string,boolean] .
En la llamada más externa, H=number y ...Tail=[string, string, boolean] .
Los tipos asignados a _... Tail_ se obtienen escribiendo literales de lista como tuplas; también se pueden usar variables de un tipo de tupla:

let tail: [number, boolean] = ["baz", false];
let l = cons(1, cons("foo", tail));

Además, las variables de tipo variadas se pueden inferir cuando se concatenan con tipos:

function car<H,...Tail>(l: [H, ...Tail]): H {
    let [head, ...tail] = l;
    return head;
}
car([1, "foo", false]);

Aquí, el tipo de l se infiere como [number, string, boolean] .
Luego H=number y ...Tail=[string, boolean] .

Límites en la inferencia de tipos

Los tipos concatenados no se pueden inferir porque el verificador no puede adivinar dónde debería estar el límite entre dos tipos:

function twoKinds<...T,...U>(total: [...T,string,...U]) {
}
twoKinds("an", "ambiguous", "call", "to", "twoKinds")

El verificador no puede decidir si asignar

  1. ...T = [string,string,string], ...U = [string]
  2. ...T = [string,string], ...U = [string,string]
  3. ...T = [string], ...U = [string,string,string]

Algunas llamadas inequívocas son una víctima de esta restricción:

twoKinds(1, "unambiguous", 12); // but still needs an annotation!

La solución es agregar anotaciones de tipo:

twoKinds<[string,string],[string,string]>("an", "ambiguous", "call", "to", "twoKinds");
twoKinds<[number],[number]>(1, "unambiguous", 12);

Pueden surgir dependencias no comprobables entre los argumentos de tipo y el cuerpo de la función, como en rotate :

function rotate(l:[...T, ...U], n: number): [...U, ...T] {
    let first: ...T = l.slice(0, n);
    let rest: ...U = l.slice(n);
    return [...rest, ...first];
}
rotate<[boolean, boolean, string], [string, number]>([true, true, 'none', 12', 'some'], 3);

Esta función se puede escribir, pero existe una dependencia entre n y las variables de tipo: n === ...T.length debe ser verdadera para que el tipo sea correcto.
No estoy seguro de si este es un código que realmente debería permitirse.

Semántica en clases e interfaces

La semántica es la misma en clases e interfaces.

TODO: Probablemente hay algunas arrugas específicas de clase en la semántica.

Asignabilidad entre tuplas y listas de parámetros

Los tipos de tupla se pueden usar para dar un tipo para descansar argumentos de funciones dentro de su alcance:

function apply<...T,U>(ap: (...args:...T) => U, args: ...T): U {
    return ap(...args);
}
function f(a: number, b: string) => string {
    return b + a;
}
apply(f, [1, 'foo']);

En este ejemplo, la lista de parámetros de f: (a: number, b:string) => string debe ser asignable al tipo de tupla instanciado para el tipo ...T .
El tipo de tupla que se infiere es [number, string] , lo que significa que (a: number, b: string) => string debe ser asignable a (...args: [number, string]) => string .

Como efecto secundario, las llamadas a funciones podrán aprovechar esta asignabilidad al expandir tuplas en parámetros de descanso, incluso si la función no tiene un tipo de tupla:

function g(a: number, ...b: [number, string]) {
    return a + b[0];
}
g(a, ...[12, 'foo']);

Tipos de tupla generados para parámetros opcionales y de descanso

Dado que las tuplas no pueden representar parámetros opcionales directamente, cuando se asigna una función a un parámetro de función que se escribe mediante un tipo de tupla, el tipo de tupla generado es una unión de tipos de tupla.
Mire el tipo de h después de haber sido curry:

function curry<...T,...U,V>(cur: (...args:[...T,...U]) => V, ...ts:...T): (...us:...U) => V {
    return ...us => cur(...ts, ...us);
}
function h(a: number, b?:string): number {
}
let curried = curry(h, 12);
curried('foo'); // ok
curried(); // ok

Aquí ...T=([number] | [number, string]) , entonces curried: ...([number] | [number, string]) => number que se puede llamar como cabría esperar. Desafortunadamente, esta estrategia no funciona para los parámetros de reposo. Estos simplemente se convierten en matrices:

function i(a: number, b?: string, ...c: boolean[]): number {
}
let curried = curry(i, 12);
curried('foo', [true, false]);
curried([true, false]);

Aquí, curried: ...([string, boolean[]] | [boolean[]]) => number .
Creo que esto podría ser compatible si hubiera un caso especial para funciones con un parámetro de resto de tupla, donde el último elemento de la tupla es una matriz.
En ese caso, la llamada a la función permitiría que los argumentos adicionales del tipo correcto coincidan con la matriz.
Sin embargo, eso parece demasiado complejo para que valga la pena.

Extensiones a las otras partes del texto mecanografiado

  1. TypeScript no permite a los usuarios escribir un tipo de tupla vacío.
    Sin embargo, esta propuesta requiere que los tipos variadic se puedan enlazar a una tupla vacía.
    Por lo tanto, Typescript deberá admitir tuplas vacías, aunque solo sea internamente.

    Ejemplos de

La mayoría de estos ejemplos son posibles como funciones de argumento fijo en el TypeScript actual, pero con esta propuesta se pueden escribir como variadas.
Algunos, como cons y concat , pueden escribirse para matrices homogéneas en Typecript actual, pero ahora pueden escribirse para tuplas heterregonas usando tipos de tuplas.
Esto sigue más de cerca la práctica típica de Javascript.

Devuelve un tipo concatenado

function cons<H,...T>(head: H, tail:...T): [H, ...T] {
    return [head, ...tail];
}
function concat<...T,...U>(first: ...T, ...second: ...U): [...T, ...U] {
    return [...first, ...second];
}
cons(1, ["foo", false]); // === [1, "foo", false]
concat(['a', true], 1, 'b'); // === ['a', true, 1, 'b']
concat(['a', true]); // === ['a', true, 1, 'b']

let start: [number,number] = [1,2]; // type annotation required here
cons(3, start); // == [3,1,2]

Tipo concatenado como parámetro

function car<H,...T>(l: [H,...T]): H {
    let [head, ...tail] = l;
    return head;
}
function cdr<H,...T>(l: [H,...T]): ...T {
    let [head, ...tail] = l;
    return ...tail;
}

cdr(["foo", 1, 2]); // => [1,2]
car(["foo", 1, 2]); // => "foo"

Funciones variadas como argumentos

function apply<...T,U>(f: (...args:...T) => U, args: ...T): U {
    return f(...args);
}

function f(x: number, y: string) {
}
function g(x: number, y: string, z: string) {
}

apply(f, [1, 'foo']); // ok
apply(f, [1, 'foo', 'bar']); // too many arguments
apply(g, [1, 'foo', 'bar']); // ok
function curry<...T,...U,V>(f: (...args:[...T,...U]) => V, ...ts:...T): (...us: ...U) => V {
    return us => f(...ts, ...us);
}
let h: (...us: [string, string]) = curry(f, 1);
let i: (s: string, t: string) = curry(f, 2);
h('hello', 'world');
function compose<...T,U,V>(f: (u:U) => U, g: (ts:...T) => V): (args: ...T) => V {
    return ...args => f(g(...args));
}
function first(x: number, y: number): string {
}
function second(s: string) {
}
let j: (x: number, y: number) => void = compose(second, first);
j(1, 2);

TODO: ¿Podría f devolver ...U lugar de U ?

Decoradores

function logged<...T,U>(target, name, descriptor: { value: (...T) => U }) {
    let method = descriptor.value;
    descriptor.value = function (...args: ...T): U {
        console.log(args);
        method.apply(this, args);
    }
}

Preguntas abiertas

  1. ¿Se mantiene la historia de asignabilidad de la tupla a la lista de parámetros? Es especialmente inestable alrededor de los parámetros opcionales y de descanso.
  2. ¿El tipo inferido será una unión de tuplas como en el caso del parámetro opcional? Debido a que bind , call y apply son métodos definidos en Function, sus argumentos de tipo deben vincularse en el momento de creación de la función en lugar del sitio de llamada bind (por ejemplo). Pero esto significa que las funciones con sobrecargas no pueden tomar ni devolver tipos específicos de sus argumentos; tienen que ser una unión de los tipos de sobrecarga. Además, Function no tiene un constructor que especifique argumentos de tipo directamente, por lo que realmente no hay forma de proporcionar los tipos correctos a bind et al. TODO: agregue un ejemplo aquí. Tenga en cuenta que este problema no es necesariamente exclusivo de las funciones variadas.
  3. ¿Deberían los parámetros de descanso estar en mayúsculas y minúsculas especiales para conservar su sintaxis de llamada agradable, incluso cuando se generan a partir de un tipo de tupla? (En esta propuesta, las funciones escritas por un tipo de tupla tienen que pasar matrices a sus parámetros de descanso, no pueden tener parámetros adicionales).
Fix Available In Discussion Suggestion

Comentario más útil

Este problema ahora está solucionado por # 39094, programado para TS 4.0.

Todos 265 comentarios

+1, ¡esto es realmente útil para la programación funcional en TypeScript! ¿Cómo funcionaría esto con argumentos opcionales o de descanso? Más concretamente, ¿se puede usar la función compose en funciones con argumentos rest o argumentos opcionales?

Buen punto. Creo que podría asignar el tipo de tupla más pequeño permitido a una función de parámetro opcional ya que las tuplas son solo objetos, que permiten miembros adicionales. Pero eso no es lo ideal. Veré si puedo encontrar el ejemplo compose y luego actualizaré la propuesta.

En realidad, los tipos de sindicatos probablemente funcionarían mejor. Algo como

function f(a: string, b? number, ...c: boolean[]): number;
function id<T>(t: T): T;
let g = compose(f, id): (...ts: ([string] | [string, number] | [string, number, boolean[]]) => number

g("foo"); // ok
g("foo", 12); // ok
g("foo", 12, [true, false, true]); // ok

Sin embargo, esto todavía rompe los parámetros de descanso.

@ahejlsberg ,

Entonces: +1: en esto. Para obtener información, esto está relacionado con (y cumpliría) # 3870. Hemos intentado implementar una API de tipo de composición en TypeScript, pero tenemos que solucionar algunas de las limitaciones señaladas en esta propuesta. ¡Esto ciertamente resolvería algunos de esos problemas!

Sin embargo, parece que a veces es posible que desee "fusionar" estos tipos de tuplas en lugar de conservarlos, especialmente con algo como componer. Por ejemplo:

function compose<T, ...U>(base: T, ...mixins: ...U): T&U {
    /* mixin magic */
}

Además, en muchos de sus ejemplos, ha estado usando primitivas. ¿Cómo cree que funciona algo más complejo, especialmente si hay conflictos?

Desafortunadamente, esta propuesta tal como está no aborda el número 3870 ni la composición de tipos, ya que el único operador de composición para los tipos de tuplas es [T,...U] . También puede escribir esto como T + ...U (que es más indicativo de lo que sucede con los tipos), pero # 3870 y su biblioteca de composición de tipos necesitan T & ...U . Creo que eso podría ser posible, pero primero necesito entender las ideas de @JsonFreeman y

Nota: Decidí usar la sintaxis [...T, ...U] porque parece la sintaxis de difusión de valor equivalente, pero T + ...U es más indicativo de lo que está sucediendo con los tipos. Si terminamos con ambos, entonces + y & podrían ser los operadores a utilizar.

Big: +1: ¡en esto!

+1 increíble! Permitiría expresar tales cosas de manera mucho más expresiva y liviana.

Mi punto en el n. ° 3870 parece ser un problema aquí. Específicamente, me preocupo por inferir argumentos de tipo para parámetros de tipo variadic.

La inferencia de argumentos de tipo es un proceso bastante complicado y ha cambiado de manera sutil a lo largo del tiempo. Cuando los argumentos se comparan con los parámetros para inferir argumentos de tipo de tipo, no hay garantías sobre el orden en el que se infieren los candidatos, ni cuántos candidatos se infieren (para un parámetro de tipo dado). En general, esto no ha sido un problema porque el resultado que se muestra al usuario no expone (en la mayoría de los casos) estos detalles. Pero si crea un tipo de tupla a partir de los resultados de la inferencia, ciertamente expone tanto el orden como el recuento de las inferencias. Estos detalles no estaban destinados a ser observables.

¿Qué tan serio es esto? Creo que depende de cómo funcione exactamente la inferencia. Cuál es el resultado de lo siguiente:

function f<...T>(x: ...T, y: ...T): ...T { }
f(['hello', 0, true], [[], 'hello', { }]); // what is the type returned by f?

@jbondc , - parece una buena idea. Lo tendré en cuenta, pero no lo exploraré aquí, porque creo que deberíamos introducir nuevos operadores de tipo uno a la vez. Tanto & como + crean nuevos tipos, pero & crea un tipo de intersección mientras que + crea un nuevo tipo de tupla (por eso prefiero la sintaxis [T,...U] lugar de T + ...U , porque [T,U] ya hace esto para los tipos).

@JsonFreeman Creo que está bien hacer una de dos cosas con parámetros de tipo repetidos:

  1. Unión de los tipos: f(['hello', 1], [1, false]): [string | number, number | boolean]
  2. No permita la inferencia de parámetros de tipo de tupla repetidos, especialmente si la inferencia de argumentos de tipo resulta complicada. Algo como esto:
f(['hello', 1], [1, false]) // error, type arguments required
f<[string, number]>(['hello', 1], [1, false]) // error, 'number' is not assignable to 'string'
f<[string | number, number | boolean]>(['hello', 1], [1, false]); // ok

Creo que las bibliotecas reales (como las extensiones reactivas a las que @Igorbek está vinculado) generalmente solo tendrán un parámetro de tipo tupla, por lo que, aunque ni (1) ni (2) son particularmente utilizables, no deberían afectar mucho al código del mundo real.

En los ejemplos anteriores, curry es el más difícil de inferir: debe omitir f: (...args:[...T,...U]) => V , inferir ...ts:...T , luego regresar y establecer ...U en lo que que queda después de consumir ...T de los parámetros de f .

Comencé a crear un prototipo de esto (sandersn / TypeScript @ 1d5725d), pero aún no he llegado tan lejos. ¿Alguna idea de si funcionará?

Me equivocaría por no permitir cualquier cosa donde la semántica no sea clara (como inferencias repetidas al mismo parámetro de tipo extendido). Eso también disipa mi preocupación anterior.

No puedo pensar en un buen mecanismo para escribir curry. Como señala, debe omitir la lista de parámetros de la primera función para consumir el argumento ...T y luego ver lo que queda. Tendría que haber alguna política para posponer las inferencias a un parámetro de tipo esparcido si no es definitivo en su lista. Podría ensuciarse.

Dicho esto, creo que vale la pena intentarlo. Existe una gran demanda de esta función.

Creo que tendría que omitir varios tipos de tuplas que ocurren en el mismo contexto (por ejemplo, de nivel superior como (...T,string,...U) => V o concatenados como [...T,...U,...T] ). Luego, puede realizar varias pasadas en los tipos omitidos, eliminando los tipos ya inferidos y volviendo a omitir los tipos que aún son ambiguos. Si en algún momento no hay un solo tipo disponible para la inferencia, deténgase y devuelva un error.

Así que sí. Complicado.

Es posible que pueda inspirarse en un problema similar. En realidad, es algo similar al problema de inferir una unión o intersección. Al inferir a un tipo de unión que incluye un parámetro de tipo que es miembro del contexto de inferencia, como en function f<T>(x: T | string[]) , no sabe si inferir a T. La manifestación prevista del tipo de unión puede haber sido string[] . Entonces, la escritura mecanografiada primero infiere a todos los demás constituyentes, y luego, si no se hicieron inferencias, infiere a T.

En el caso de la intersección, es aún más difícil porque es posible que deba dividir el tipo de argumento entre los diferentes constituyentes de la intersección. TypeScript no hace inferencias sobre los tipos de intersección en absoluto.

¿Qué pasaría si solo permitiera la tupla de propagación si es el último tipo de su secuencia? Entonces [string, ...T] estaría permitido, pero [...T, string] no.

Si entiendo correctamente, esto realmente resolvería la historia de la mezcla en TypeScript. ¿Estoy en lo correcto en este entendimiento?

Quizás. ¿Puede dar un ejemplo? No soy fluido con los patrones de mezcla.

La sintaxis de una variable de tipo variable es ... T donde T es un identificador que por convención es una sola letra mayúscula, o T seguida de un identificador PascalCase.

¿Podemos dejar el caso de un identificador de parámetro de tipo al desarrollador?

@ aleksey-bykov +1. No veo una razón por la que ese no debería ser el caso.

Los desarrolladores con experiencia en Haskell lo agradecerían.

Lo siento, esa oración se puede analizar de forma ambigua. Quise decir 'o' para analizar con precisión: "por convención (una sola letra mayúscula || T seguida de un identificador PascalCase)". No propongo limitar el caso de los identificadores, solo señalar la convención.

Sin embargo, por lo que vale, tengo experiencia en Haskell y no me gusta romper las convenciones del idioma en el que estoy escribiendo.

Perdón por descarrilar. Mi última pregunta curiosa (si no le importa que la pregunte) ¿cuál es la "convención" de TypeScript que podría romperse y a quién le preocupa?

@sandersn

Esto debería escribir check, asumiendo que T & ...U significa T & U & V & ... (que es el comportamiento intuitivo).

function assign<T, U, ...V>(obj: T, src: U, ...srcs: ...V): T & U & ...V {
  if (arguments.length < 2) return <T & U & ...V> obj

  for (const key of Object.keys(src)) {
    (<any> obj)[key] = (<any> src)[key]
  }

  if (arguments.length === 2) return <U> obj
  return mixin<T, ...V>(obj, ...srcs)
}

O en un archivo de definición:

interface Object {
    assign<T, U, ...V>(host: T, arg: U, ...args: ...V): T & U & ...V
}

@ aleksey-bykov, la convención de la que estoy hablando es el caso de los identificadores de parámetros de tipo. ¿A quién le preocupa? Las personas que tienen que leer un nuevo código Typecript que nunca antes habían visto: las convenciones ayudan a los nuevos lectores a comprender el nuevo código más rápido.

@sandersn Lo que @ aleksey-bykov tuvo la impresión fue que lo siguiente sería _sintácticamente_ inválido:

function assign<a, b, ...cs>(x: a, y: b, ...zs: ...cs): a & b & ...cs;

Las operaciones & @isiahmeadows & y | sobre tipos no están cubiertas en esta propuesta, aunque debería agregarlas a preguntas abiertas / trabajo futuro si no lo he hecho. En este momento, el único operador propuesto es la concatenación: [THead, ...TTail] .

Una diferencia es que la concatenación todavía produce un tipo de tupla mientras que & y | producen tipos de intersección y unión respectivamente.

@sandersn Mi assign en TypeScript sería trivial de cambiar con eso.

A pesar de que:

  1. La intersección sería similar a la concatenación, aunque es más como concatenar diccionarios que concatenar listas. Los tipos variadic pueden implementarse sobre la maquinaria existente allí.
  2. La unión sería como una intersección, excepto que solo mantiene las partes comunes. Una vez más, los tipos variados podrían implementarse sobre la maquinaria existente.

@isiahmeadows Una intersección no es en general una concatenación de diccionarios. Eso solo es cierto para una intersección de tipos de objetos, pero no, por ejemplo, una intersección de uniones. Las uniones tampoco son lo mismo que tomar las propiedades que los objetos tienen en común. Los dos se caracterizan mejor por el conjunto de valores que los habita.

@sandersn Estoy un poco confundido acerca de la inferencia de argumentos de tipo con tipos variados. ¿Qué se debe inferir aquí?

function foo<...T>(...rest: ...T): ...T { }
foo('str', 0, [0]);

¿El resultado es [string, number, number[]] ? Eso significaría que debe confiar en la inferencia de argumentos de tipo para agregar candidatos en un orden de izquierda a derecha, lo cual no es una suposición trivial. También sería la primera vez que el sistema de tipos muestra al usuario la lista de candidatos de inferencia.

Sé que es una propuesta experimental / temprana, pero podríamos discutir la sintaxis ...T para los parámetros de descanso. Desde mi perspectiva, realmente no funciona.
Entonces la sintaxis propuesta es:

declare function f<...T>(...a: ...T);

comparemos con la sintaxis existente de los parámetros rest:

declare function f(...a: number[]);

por lo que el tipo de parámetro a que captura argumentos de descanso es number[] , por lo que podemos entender claramente que es una matriz. Por analogía, puedo inferir que ...T de la propuesta también representa una matriz. Pero eso no es muy obvio.
A continuación, digamos que podríamos definir parámetros de descanso más restrictivos:

declare function f(...a: [number, string]);
// same as
declare function f(c: number, d: string); // or very close to

Entonces, todavía vemos que el tipo de a es una tupla (que es una matriz).

Mi propuesta es utilizar una forma más coherente de tratar la noción de ...T para representarla como una "lista abstracta ordenada de tipos". Y utilícelo de la misma manera que usamos el operador de propagación:

var a: [number, string] = [1, "1"];
var b = [true, ...a]; // this must be [boolean, number, string], but it doesn't work :)

Entonces ...a en caso de variable, es solo 1, "1" .

Mi sintaxis para definir parámetros de descanso por ...T noción:

declare function f<...T>(...a: [...T]);
declare function g<H, ...T>(head: H, ...tail: [...T]): [H, ...T];

Para mí tiene mucho más sentido.

@Igorbek He estado ejecutando en la suposición de que declare function f<...T>(...a: ...T); ya funcionó así. Pero no veo que declare function f(...a: [number, string]); se use mucho.

Para ser más claro.

Sintaxis propuesta originalmente para los parámetros de descanso:

function func<...T>(...a: ...T)

Si puedo hacer esto

function g<...T>(...a: ...T): [number, ...T] { ... }

entonces podré hacer esto:

function f<...T>(...a: ...T): [...T] { return a; }

Entonces, el tipo de a es [...T] (regresamos así), pero lo definimos como ...T en la firma.
Podríamos decir que ...T y [...T] son iguales, pero no funciona en caso de variables.
Para variables:

var a = [1, 2];
[a] === [[1,2]];
[...a] === [1, 2];
f(...a) === f(1, 2)
...a === 1, 2 // virtually

Si aplicamos lo mismo a los parámetros de descanso estándar

function f(...a: number[]): number[] { return a; }

el tipo de a es number[] (por tipo de retorno), igual que se definió en la firma.

@isiahmeadows sí, function f(...a: [number, string]) no funciona. Acabo de desarrollar pensamientos sobre cómo podemos tratar los parámetros de descanso.

Entonces, yendo más allá. Para definir explícitamente los parámetros de tipo, se propuso la siguiente sintaxis:

function f<...T, ...U>()
f<[number, string], [boolean, number]>();

Se convierte en:

f<...[number, string], ...[boolean, number]>();

Entonces esto también podría funcionar:

function g<T1, T2, T3>()

g<A, B, C>();
// same as
g<...[A, B, C]>();
g<...[A], ...[B, C]>(); 
g<...[A], B, C, ...[]>();

@JsonFreeman, así es como funciona mi prototipo, sí. Pero no estoy lo suficientemente familiarizado con el algoritmo de inferencia de tipos para entender por qué funciona. En otras palabras, la cuestión no es si la inferencia de izquierda a derecha es una suposición _trivial_ sino correcta. Para el caso de identidad, la respuesta es sí, pero no sé si puede construir casos en los que la respuesta sea no.

¿También puede trabajar con un ejemplo de un conjunto expuesto de candidatos de inferencia de tipo? Como dije, no entiendo muy bien el funcionamiento del algoritmo de inferencia, por lo que un ejemplo me ayudaría a ver a qué te refieres.

Y aun mejor:

function<...T>(...a: T): T;
// same as
function<...T>(...a: [...T]): T;

Sugiero prefijar [] al identificador de tipo para indicar el resto de parámetros de tipo.

function fn<R, []T>(...a:[]T): R;

Es 1 carácter más corto que ...T y (en mi opinión) hace menos ruido visual.

@ aleksey-bykov En realidad, soy de la opinión opuesta al respecto. No encaja con la sintaxis del parámetro rest existente, por lo que creo que también es menos claro de un vistazo.

[...T] / T como tipo de parámetro de matriz de descanso me parece mucho mejor. Una vez más, compare con array y su operador sprad:

| matrices | tipos (de propuesta) | tipos (mi actualización) |
| --- | --- | --- |
| var x = [1,2] | no | T = [T1, T2] |
| [0, ...x] === [0,1,2] | [T0, ...T] === [T0, T1, T2] | [T0, ...T] === [T0, T1, T2] |
| f(x) === f([1, 2]) | no | f<T>() === f<[T1, T2]>() |
| f(...x) === f(1, 2) | f<...T>() === f<[T, T2]> ? | f<...T>() === f<T1, T2> |
| f(0, ...x) === f(1, 2) | f<T0, ...T>() === f<T0, [T, T2]> ? | f<T0, ...T>() === f<T0, T1, T2> |

De propuesta

function g<...T>(...x: ...T) {
 // being called as g(1, "a");
  var a: ...T; // [number, string] ?
  var b: [number, ...T]; // [number, number, string]
  var c: [...T]; // [number, string] - same as a ? so [...T] is same as ...T - weird
}

De mi actualización

function g<...T>(...x: T) {
 // being called as g(1, "a");
  var a: T; // [number, string]
  var b: [number, ...T]; // [number, number, string]
  var c: [...T]; // [number, string]
}

La actualización se ve mejor ahora en mi opinión. Las listas para representar tipos suenan muy bien, pero incluso los Lisps escritos no llegan tan lejos (tipos homoicónicos, ¿alguien?: Sonrisa :).

Recibo el encanto de la pureza, pero también miro el aspecto pragmático. Las listas también serían relativamente fáciles de implementar por sí mismas, pero no encajan con el resto del lenguaje. Es casi como los numerosos intentos de implementar mónadas en Java (el lenguaje) o lambdas en C: siempre resultan increíblemente feos y pirateados.

@sandersn Puedo intentar explicar a qué me refiero exponiendo la lista de candidatos. La inferencia de argumentos de tipo genera una lista de candidatos para cada parámetro de tipo. Luego verifica si algún candidato es un supertipo de todos los demás y, de ser así, ese candidato es el ganador. Entonces, en el siguiente ejemplo:

function foo<T>(a: T, b: T): T {}
foo(["hi", 0], ["", ""]);

Los argumentos se escribirán y luego se inferirán para cada parámetro. Se generarán dos candidatos, a saber, (string | number)[] y string[] . Pero el primero ganará porque es un supertipo del segundo. Y como resultado, el usuario nunca observa que string[] alguna vez estuvo en la imagen. Hay una inferencia para T , y todos los demás candidatos son invisibles. Esto significa que hay dos cosas invisibles para el usuario, a saber, el orden de los candidatos y la multiplicidad de los candidatos.

Aquí hay un problema con las multiplicidades si confía en la lista de candidatos como su lista de elementos en la tupla indicada por ...T :

function foo<...T>(...rest: ...T): ...T
foo(0, 1);

Creo que desearía inferir [number, number] para T dada la intención de su propuesta tal como la entiendo. Pero debido al control de contenido en la línea https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L6256 , el candidato number solo se agregará una vez, y T se deducirá como [number] . Este es el tema de la multiplicidad del que estaba hablando.

En cuanto al orden, es de izquierda a derecha. Pero hay varias pasadas y los argumentos se volverán a procesar si contienen expresiones de función que se escribirán contextualmente. Si hay n argumentos que contienen expresiones de función de tipo contextual, entonces hay n + 1 pasadas sobre los argumentos. Un ejemplo es Array.prototype.reduce, donde el parámetro initialValue se escribe e infiere efectivamente antes de la devolución de llamada, a pesar de que está a la derecha. Entonces, algo como lo siguiente podría ser un problema para la propuesta:

function foo<...T>(...rest: ...T): ...T
foo(x => x, 0);

Intuitivamente, T debería ser [(x: any) => any, number] , pero si confía en el orden en que se agregan los candidatos, será [number, (x: any) => any] . Esto se debe a que la inferencia de argumentos de tipo generalmente se hace de izquierda a derecha, pero las funciones sujetas a la escritura contextual se aplazan hasta el final.

Tanto la multiplicidad como los problemas de orden que he explicado son ejemplos de la aparición de la lista de candidatos. @ahejlsberg seguramente será una buena persona para preguntar sobre esto también y, de hecho, puede ayudar a explicar, confirmar o refutar todo lo que he dicho.

@JsonFreeman, ¿por qué crees que sería un problema?
Se puede implementar mediante la introducción virtual de tipos genéricos adicionales para cada argumento fáctico restante e inferir en función de la función con una longitud de parámetros fija.
Por ejemplo,

function foo<...T>(...rest: T) { ... }
foo(x => x, 0);
// to infer, the following function is used
function foo2<T0, T1>(rest0: T0, rest1: T1) { ... }
foo2(x => x, 0);
// inferred as
foo2<(x: any) => any, number>
// T0 = (x: any) => any
// T1 = number
// T = [T0, T1] = [(x: any) => any, number]

Por cierto, ¿podemos inferir que x => x sea ​​del tipo { <T>(x: T): T; } ?

@Igorbek Creo que su sugerencia sobre los parámetros de tipo de fabricación (al menos como intuición, independientemente de cómo se implemente) es la forma correcta de hacerlo. Podría inferir una secuencia de tipos para T , donde cada elemento de la secuencia tiene un índice y una lista de candidatos (esta es una forma alternativa de implementar lo que mencionó).

Sin embargo, mi punto fue que no creo que esto sea lo que sucedería naturalmente si simplemente reutilizara la lista de candidatos de inferencia como la tupla inferida. Requeriría una mecánica explícita para que suceda lo correcto.

Para su punto sobre { <T>(x: T): T; } , eso no se generaliza bien para escribir cosas como x => foo(x) donde foo es una función. Necesitaría saber el tipo de x para hacer la resolución de sobrecarga para foo .

Un pequeño paso fuera de la batalla con las reglas de inferencia del verificador de tipos.
Tengo un comentario / sugerencia sobre la sintaxis. Creo que hay dos opciones consistentes pero mutuamente excluyentes:

1. Argumentos formales del tipo de descanso

Si elegimos esta forma:

type F<...Args> = (...args:...Args) => ...Args

entonces deberíamos usarlo como

var a:  F // a: () => []
var b:  F<number> // b: (arg: number) => [number]
var c:  F<number, string> // c: (arg1: number, arg2: string) => [number, string]
...

Así serán verdaderos tipos de resto formales. Deben usarse solo en la última posición de la sección de parámetro de tipo formal.

2. Argumentos de resto escritos con tuplas

(...args:[string, number]) => boolean    IS EQUIVALENT TO   (s: string, n: number) => boolean

En este caso, siempre tenemos un número fijo de ranuras en la sección de parámetros de tipo formal.

function f<T>(...args: T): T {
    return args;
}

inferimos que T debería ser un tipo de tupla si se cumple alguna de las condiciones:

  1. T se usa para parámetros de descanso como (... args: T) => T
  2. T se utiliza en la composición de la propagación como [... T] o [número, ... T, cadena]

Por lo tanto, no necesitamos usar puntos suspensivos en la sección de parámetro de tipo formal (podemos inferirlo incluso _sintácticamente_ sin ningún verificador de tipo)

en este caso, podemos escribir también

function f<T>(...args: [...T]): [...T] {
    return args;
}

pero es redundante.

Personalmente, me gustaría ver el último implementado en TypeScript. @JsonFreeman , @sandersn?

@Artazor Creo que se reduce a la expresividad, y no creo que los dos enfoques sean necesariamente equivalentes. El segundo incluye la capacidad de difundir un parámetro de tipo de descanso dentro de un tipo de tupla, mientras que el primero no parece hacerlo.

Creo que para las referencias de tipo genérico, es solo una cuestión de decidir dónde y sintácticamente cómo usar un parámetro de tipo de descanso. Esto debería decidirse para todos los constructores de tipos que toman una secuencia de tipos (tuplas, firmas, referencias de tipos genéricos).

Para firmas genéricas, es más complicado debido a la inferencia de argumentos de tipo. ¿Y si tuviera lo siguiente?

function callback(s: string, n: number): void { }
declare function foo<...T>(cb: (...cbArgs: T) => void, ...args: T): [...T];

foo(callback, "hello", 0, 1);

¿Qué vuelve foo? Mi punto es que la gente espera que las reglas genéricas sean las mismas para los tipos genéricos y las firmas genéricas, pero si hace que los tipos genéricos sean más expresivos, la inferencia de argumentos de tipo necesita una forma de manejarlo. Esto puede ser solo una cuestión de identificar formalmente los casos que son difíciles para la inferencia de argumentos de tipo y requerir que el usuario pase argumentos de tipo explícitos en estos casos.

En términos de mi opinión, creo que su opción 1 es mejor. Personalmente, no veo el uso de tipos de tuplas como parámetros de descanso. Creo que un parámetro de descanso solo debería ser un tipo de matriz o un parámetro de tipo de descanso, porque se supone que tiene una longitud variable. También me gusta el concepto de que un parámetro de tipo de descanso es una secuencia abstracta de tipos, no asociada con algo que ya existe en el sistema de tipos.

Mi filosofía sobre las tuplas es que representan un subconjunto de valores de matriz donde se conoce la longitud. Esos valores de matriz son entidades reales en tiempo de ejecución. No me gusta la idea de usarlos como una especie de dispositivo de sistema de tipos para representar una secuencia abstracta de tipos (por ejemplo, la secuencia de parámetros en una firma). Pero si se le permite difundir un parámetro de tipo de descanso en una tupla es una historia diferente.

Me gusta la propuesta de tupla porque es más poderosa y resuelve más casos de uso, también es muy intuitivo que puedo difundir una tupla como parámetro de descanso porque las tuplas son solo matrices y cuando llamo a una función con un parámetro de descanso puedo difundir la matriz. El sistema de tipos coincidiría mejor con mi comprensión del código.

@JsonFreeman en su caso, foo devolvería [string, number, number] ya que eso se inferiría de ...args , el tipo cb inferido sería (string, number, number) => void y la devolución de llamada pasada simplemente ignoraría el último argumento que es muy común tanto en TS como en JS.

No me gusta la idea de usarlos como una especie de dispositivo de sistema de tipos para representar una secuencia abstracta de tipos

Eso es exactamente lo que son, JS no sabe de tuplas, solo TS. Para TS, una tupla es una secuencia de tipos.

También me gusta el enfoque basado en tuplas. Especialmente si pudiéramos tener firmas de funciones compatibles como esa:

// all are equivalent
(a: A, b: B, c: C) => R;
(a: A, b: B, ...rest: [C]) => R;
(a: A, ...rest: [B, C]) => R;
(...args: [A, B, C]) => R;

// this is more complicated 
(a: A, ...rest: T[]) => R;
(...args: [A, ...T]) => R; // no in current syntax

Este último no lo podemos expresar con la sintaxis actual, pero podríamos hacerlo si tuviéramos adoptado el # 6229.
Entonces, para mí, parece que una forma adecuada es usar tuplas y unificar tuplas para expresar más. Sin tuplas más expresivas, sería difícil tener algo como [...T, ...T] porque T como tupla tiene una longitud abierta.

@JsonFreeman para su ejemplo, @Pajn mostró exactamente como lo entendí: no hay ningún problema visible al inferir estos tipos.

@JsonFreeman Será mejor que use esa sintaxis

declare function foo<T>(cb: (...cbArgs: T) => void, ...args: T): T;
declare function foo<T>(cb: (...cbArgs: T) => void, ...args: T): [...T]; // same

Hm, probablemente puede introducir alguna ambigüedad:

declare function foo<T>(...args: T): T;
foo(1); // T is [number] or number[]?

// however, here it'd be more explicit
declare function foo<T>(...args: T[]): T[];
foo(1); // T is number[]

// and here
declare function foo<T>(...args: [...T]): T;
foo(1); // T is [number]

Podría apoyar la idea de difundir un parámetro de tipo de descanso en una tupla. Pero no estoy seguro de querer que un parámetro de tipo de descanso se interprete implícitamente como una tupla. El ejemplo de @Pajn aún funcionaría si se permite que los parámetros de tipo de descanso se distribuyan en todas las posiciones de secuencia de tipo (tuplas, listas de parámetros, argumentos de tipo).

@Igorbek Tienes razón sobre la ambigüedad de tu primer ejemplo. Sin embargo, su tercer ejemplo también es problemático. Dada una secuencia como number, string , hay 2 posibles instancias de la firma. Es decir, (arg1: number, arg2: string) => [number, string] así como (arg1: [number, string]) => [number, string] (adoptando la interpretación de tupla implícita por el bien del ejemplo).

La otra cosa extraña acerca de la interpretación de tupla implícita es la siguiente: digamos que tiene un parámetro de tipo de descanso T que se instancia en number, string . Ahora digamos que los pasa como argumentos de tipo, Foo<T> . ¿Debe interpretarse como Foo<[number, string]> mientras que Foo<...T> es Foo<number, string> ? Hay un argumento para esto, ya que sería extender el operador de propagación al sistema de tipos. Pero aún prefiero que la versión de tupla se represente como Foo<[...T]>

Llámame loco, pero siento algunos defectos fundamentales con la idea de usar
tuplas. ¿Qué sucede si intenta difundir un tipo de tupla entre demasiados
parámetros? ¿Como esto?

declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

Además, ¿qué sucede si los parámetros de tipo son del tipo incorrecto o se utilizan en
lugares inusuales y potencialmente erróneos?

// 1. unusual place
declare foo<T>(x: T, ...ys: [...T]): void

// 2. bad type
declare foo<T>(...xs: [...T]): void
foo<number>(2)

El primer ejemplo es directamente relevante para la función # aplicar (y podría ser un
error), y el segundo es un error no obvio que no se podrá compilar,
y no trivial de detectar con Intellisense.

El domingo 28 de febrero de 2016 a las 03:04, Jason Freeman [email protected] escribió:

La otra cosa extraña acerca de la interpretación de tupla implícita es esta: digamos
tiene un parámetro de tipo de descanso T que se instancia en número, cadena.
Ahora di que pasas esos como argumentos de tipo, Foo. Es que va a ser
interpretado como Foo <[número, cadena]> mientras que Foo <... T> es Foo cadena>?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189817561
.

@JsonFreeman

Sin embargo, su tercer ejemplo también es problemático. Dada una secuencia como number, string , hay 2 posibles instancias de la firma. Es decir, (arg1: number, arg2: string) => [number, string] así como (arg1: [number, string]) => [number, string] (adoptando la interpretación de tupla implícita por el bien del ejemplo).

De mi tercer ejemplo que está claro, solo se puede interpretar como (...args: [number, string]) => [number, string] :

declare function foo<T>(...args: [...T]): T;
foo(1, "a"); // T is [number, string]
const result: [number, string] = foo<[number, string]>(1, "a");

// however, it is assignable to/from the following signatures:
const f1: (arg1: number, arg2: string) => [number, string] = foo<[number, string]>;
const f2: (arg1: number, ...rest: [string]) => [number, string] = foo<[number, string]>;

La otra cosa extraña acerca de la interpretación de tupla implícita es la siguiente: digamos que tiene un parámetro de tipo de descanso T instanciado en number, string .

T no se puede instanciar en number, string ya que es una verdadera tupla. Debe ser [number, string] .

Ahora digamos que los pasa como argumentos de tipo, Foo<T> . ¿Debe interpretarse eso como Foo<[number, string]> mientras que Foo<...T> es Foo<number, string> ?

Verdadero. Sin embargo, tener <...T> parece redundante para estos casos de uso particulares que estamos discutiendo (captura tipos posicionados para argumentos de descanso). Sin embargo, digamos que lo tenemos.

Hay un argumento para esto, ya que sería extender el operador de propagación al sistema de tipos. Pero aún prefiero que la versión de tupla se represente como Foo<[...T]>

Hay dos casos en los que podríamos usar esa sintaxis:

// in a signature declaration
declare function foo<[...T]>(...args: [...T]): [...T];
// and when type instantiated, so in the usage
type T = [number, string]
foo<T>();
foo<[...T]>();
// the latter can virtually be replaced as
type _T = [...T]; // which is a type operation that should produce [number, string]
foo<_T>();
// and more
type Extended = [boolean, ...T]; // [boolean, number, string]

Entonces, para el uso, no es más que un operador de tipo como | , & o [] . Pero en la declaración, esa sintaxis se puede interpretar como T extends any[] o cualquier tipo de base para todas las tuplas, para indicar que debe ser un tipo de tupla.

@isiahmeadows

¿Qué sucede si intenta difundir un tipo de tupla entre demasiados
parámetros? ¿Como esto?

declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2); // ok, foo<[number]> is of type (...args: [number]) => void
// [1, 2] is being passed in place of args
// is [1, 2] which is [number, number] assignable to [number]? yes, with current rules
// no error

Además, ¿qué sucede si los parámetros de tipo son del tipo incorrecto o se utilizan en
lugares inusuales y potencialmente erróneos?

// 1. unusual place
declare foo<T>(x: T, ...ys: [...T]): void
// 1. [...T] can be interpret as a type constraint "must be a tuple type"
// 2. if we call with type specified
foo<number>(1); // number doesn't meet constraint
foo<[number]>(1, 2); // argument of type 'number' is not assignable to parameter 'x' of type '[number]'
foo<[number]>([1], 2); // ok
// 3. if we call without type, it must be inferred
foo(1); // according to current rules, T would be inferred as '{}[]' - base type of all tuples
        // so, argument of type 'number' is not assignable to parameter 'x' of type '{}[]'
foo([1, 2], 2); // T is inferred as '[number, number]
                // rest arguments of type '[number]' are not assignable to rest parameters 'ys' of type '[number, string]'
foo([1], 2, 3); // T is '[number]',
                // x is of type '[number]',
                // ys is of type '[number]',
                // rest arguments are of type '[number, number]' which is assignable to '[number]',
                // no error

// 2. bad type
declare foo<T>(...xs: [...T]): void
foo<number>(2); // type 'number' doesn't meet constraint

Todavía no veo el beneficio de representar estas cosas como tuplas. Además, creo que deberían declararse como <...T> y no <T> . Como dije antes, no veo los tipos de tupla como un dispositivo apropiado para usar para secuencias de tipos de longitud arbitraria en el sistema de tipos. Todavía no estoy convencido de que esto sea necesario para la expresividad que la gente quiere.

Estoy de acuerdo en que podría ser más expresivo, pero tener el operador 'spread' en la posición de los parámetros de tipo nos limitará a capturar argumentos de descanso solo una vez, al igual que no podemos tener parámetros de descanso dos veces. Entonces, dado <...T> y <A, B, C> , T los capturará como [A, B, C] . Y no podríamos expresar <...T, ...U> ya que sería ambiguo - [A, B, C], [] o [A, B], [C] o ... etc.

Digamos que quería expresar una función con el siguiente comportamiento:

declare function foo(a: A, b: B): R;
declare function boo(c: C, d: D, e: E): U;

let combined: (a: A, b: B, c: C, d: D, e: E) => [R, U] = combine(foo, boo);

// so the signature could be:

declare function combine<R, U, ???>(
  f1: (...args: [...T1]) => R,
  f2: (...args: [...T2]) => U):
    (...args: [...T1, ...T2]) => [R, U];

// if ??? is '...T1, ...T2'
combine<R, U, A, B, C, D, E> // what will be T1 and T2 ?
combine<R, U, ...[A, B, C], ...[D, E]> // ok ? so we will preserve spread to specific positions. so then
combine<...[R, U], A, ...[B, C, D], E> // will be restricted.
// however, ES6 allows to do it with function arguments
f(1, 2, 3);
f(...[1, 2], 3);
f(...[1], ...[2, 3]);

// if ??? is 'T1 extends TupleBase, T2 extends TupleBase'
// or just '[...T1], [...T2]' as a shortcut for such constraints
combine<R, U, [A, B, C], [D, E]> // pretty explicit, and doesn't occupy spread operator for type arguments

Ok, ahora veo cómo lo estás pensando. Parece que lo que propones es en realidad una característica diferente de lo que pensaba. En lugar de agregar una nueva construcción para capturar una secuencia de parámetros de tipo, solo desea que los tipos de tupla se puedan extender porque ya representan una secuencia de tipos. De esa manera es posible pasar múltiples tuplas de varias longitudes de una manera más transparente.

En javascript, es más como function foo([...rest]) { } lugar de function foo(...rest) { } .

Eso tiene más sentido para mí ahora, gracias por explicarme. Creo que es un enfoque razonable.

@JsonFreeman ¡Exactamente!

@JsonFreeman Pregunta: ¿por qué [1, 2] satisfacer [number] ? Eso me parece muy extraño. Que realmente funcione sería muy sorprendente. No es para nada seguro.

Sin embargo, no es que tenga nada en contra de que las tuplas se usen para tipos variados (soy neutral, también sé honesto).

@isiahmeadows ¿de qué manera [1, 2] no es sustituible por [number] ? Definitivamente es un subtipo. Es lo mismo que { x: 1, y: 2 } es un { x: number } válido

Bueno. Lo concederé parcialmente, pero tengo en cuenta Function.prototype.apply, que acepta una tupla de argumentos.

interface Function<T, U, V> {
    (this: T...args: [...U]): V;
    apply(object: T, args: U): V;
}

Si la persona que llama arroja un TypeError en demasiados argumentos, pasar demasiados dará como resultado un error de tiempo de ejecución, no un error de compilación como debería.

¿No es bastante raro que cualquier función JS arroje TypeError cuando se le pasan demasiados argumentos? ¿Cuáles son algunos ejemplos?

@isiahmeadows como ejemplo abstracto, entendí que el error que le preocupa es:

function f(x: number): void {
  // throw if too many arguments
}
f.apply(undefined, [1,2,3]); // runtime error, no compile-time error
f(1,2,3) // compile-time error and runtime error.

¿Es eso correcto?

@sandersn , creo que TypeError en demasiados argumentos es algo que viola el espíritu del JS, ya que generalmente pasamos funciones con argumentos menos formales que los reales que se pasarán a esta función. Simplemente no los usamos. Por ejemplo Array.prototype.forEach

¿Qué pasa con el curado de funciones? Eso es probablemente mucho más común, con Ramda
y lodash / fp.

El lunes 29 de febrero de 2016 a las 13:45, Anatoly Ressin [email protected] escribió:

@sandersn https://github.com/sandersn , creo que TypeError también está activado
muchos argumentos es algo que viola el espíritu del JS, ya que
usualmente pasan funciones con argumentos menos formales que los reales que
pasar a esta función. Simplemente no los usamos. Por ejemplo
Array.prototype.forEach

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190327066
.

@isiahmeadows Yo diría que el curry basado en arguments.length es muy inestable y propenso a errores en tiempo de ejecución. El curry real es a prueba de argumentos adicionales:

var plus = x => y => x + y
console.log(plus(3)(4)) // 7
console.log(plus(3,10)(4,20)) // still 7

Cuando paso mi función con firma fija como devolución de llamada a algún lugar, lo pienso de la siguiente manera: 'mi función espera _ al menos_ esos argumentos'

¿Qué pasa con cosas como foldl ?

const list = [1, 2, 3]
console.log(foldl((a, b) => a + b, 0, list))
console.log(foldl((a, b) => a + b, 0)(list))
console.log(foldl((a, b) => a + b)(0, list))
console.log(foldl((a, b) => a + b)(0)(list))

Eso es muy común en la programación funcional. Y omitiendo el último
El argumento es bastante común.

El lunes 29 de febrero de 2016 a las 13:52, Anatoly Ressin [email protected] escribió:

@isiahmeadows https://github.com/isiahmeadows Yo diría que curry
basado en aruments.length es muy inestable y propenso a errores en tiempo de ejecución.
El curry real es a prueba de argumentos adicionales:

var más = x => y => x + y
console.log (más (3) (4)) // 7
console.log (más (3,10) (4,20)) // sigue siendo 7

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Si desea pasar eso como una devolución de llamada a, digamos, map (trabajando sobre una lista
de listas), probablemente querrá currylo.

El lunes 29 de febrero de 2016 a las 13:59, Isiah Meadows [email protected] escribió:

¿Qué pasa con cosas como foldl ?

const list = [1, 2, 3]
console.log(foldl((a, b) => a + b, 0, list))
console.log(foldl((a, b) => a + b, 0)(list))
console.log(foldl((a, b) => a + b)(0, list))
console.log(foldl((a, b) => a + b)(0)(list))

Eso es muy común en la programación funcional. Y omitiendo el último
El argumento es bastante común.

El lunes, 29 de febrero de 2016, 13:52 Anatoly Ressin [email protected]
escribió:

@isiahmeadows https://github.com/isiahmeadows Yo diría que curry
basado en aruments.length es muy inestable y propenso a errores en tiempo de ejecución.
El curry real es a prueba de argumentos adicionales:

var más = x => y => x + y
console.log (más (3) (4)) // 7
console.log (más (3,10) (4,20)) // sigue siendo 7

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Creo que se trata principalmente de eso:

type T = [number, string];
var a: T = [1, "a", 2]; // valid

// in this cases tuple types or parameter types cannot be inferred:
f(...a, true); // you could think number,string,boolean were passed, but weren't
const c = [...a, true]; // you could think that is of type [number, string, boolean] but it's not
// according to current rules, the best inferred types might be [number, string, number|string|boolean]

// same manner with variadic kinds, types are constructed properly:
type R = [...T, boolean]; // [number, string, boolean]

Por eso he propuesto el n. ° 6229

La pregunta de si [1, 2] satisface [number] es válida para preguntar y debatir. Pero, ¿qué tiene que ver con la función de tuplas untables?

Es si una aplicación variada de tuplas debería ignorar argumentos adicionales
o no. Esta función sobrecargada debería explicar más mi preocupación.

declare function foo(x: number, ...args: string[]): void
declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

// This will always fail
declare function foo(x: number, ...args: string[]): void
declare function foo<T>(x: T): void
foo<number>(1, 2)

El lunes 29 de febrero de 2016 a las 18:47, Jason Freeman [email protected] escribió:

La pregunta de si [1, 2] satisface [número] es válida.
y debate. Pero, ¿qué tiene que ver con la función de tuplas untables?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

Y esta es la razón por la que, por razones prácticas, preferiría descansar como parámetro
tipos variadic.

El lunes 29 de febrero de 2016 a las 19:00, Isiah Meadows [email protected] escribió:

Es si una aplicación variada de tuplas debería ignorar extra
argumentos o no. Esta función sobrecargada debería elaborar más de mi
preocupación.

declare function foo(x: number, ...args: string[]): void


declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

// This will always fail
declare function foo(x: number, ...args: string[]): void
declare function foo<T>(x: T): void
foo<number>(1, 2)

El lunes 29 de febrero de 2016 a las 18:47 Jason Freeman [email protected]
escribió:

La pregunta de si [1, 2] satisface [número] es válida.
y debate. Pero, ¿qué tiene que ver con la función de tuplas untables?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

@JsonFreeman, eso se debe a que se extiende el operador para tipos y matrices / tuplas. Si se permite el operador de tipo de propagación en forma de "tipos dados A , B y T = [A] , entonces [...T, B] construirá [A, B] " (que se propone implícitamente), entonces no estaría alineado con el operador de distribución de matriz / tupla. Dados var a: [A] y var b: B , no se puede probar que el tipo de expresión [...a, b] sea ​​del tipo [A, B] . De acuerdo con las reglas actuales de tuplas, se podría probar que es del tipo [A, A|B] .
¿Tiene sentido para ti? O puedo crear una tabla de comparación para resaltar esa falta de coincidencia.

@Igorbek Entiendo lo que estás diciendo. En última instancia, se debe al hecho de que el compilador tiene un conocimiento perfecto de los tipos con los que está tratando, pero no un conocimiento perfecto de los valores. En particular, en su ejemplo, el valor a tiene una longitud desconocida, mientras que el tipo [A] tiene una longitud conocida. Esta fue una de las razones por las que inicialmente me sentí incómodo con el uso de tipos de tuplas para este propósito. Pero no estoy seguro de que sea un problema grave.

@isiahmeadows Veo lo que está preguntando, pero ¿por qué el problema es más claro con los parámetros de tipo de descanso? Si tiene más argumentos que argumentos de tipo, se puede hacer la misma pregunta.

El tipo de solución segura sería más consistente con el resto de los
idioma si imita la sintaxis del argumento.

Mi punto es que si estás difundiendo efectivamente un parámetro de descanso, obtienes
exactamente los tipos de argumentos y nada más. Las funciones al curry tienen un retorno
tipo depende del tipo de argumento. Entonces, si aplicas demasiados argumentos
para aplicar parcialmente una función de curry, obtendrá una función completamente diferente
escribe. En cambio, manejar tipos de descanso como tuplas conduciría a errores de tiempo de ejecución,
que nunca son buenos.

El martes, 1 de marzo de 2016, 06:07 Jason Freeman [email protected] escribió:

@Igorbek https://github.com/Igorbek Entiendo lo que estás diciendo.
En última instancia, se debe al hecho de que el compilador tiene un conocimiento perfecto
de los tipos con los que se trata, pero no un conocimiento perfecto de los valores. En
En particular, en su ejemplo, el valor a tiene una longitud desconocida, mientras que el
el tipo [A] tiene una longitud conocida. Esta fue una de las razones por las que inicialmente
incómodo sobre el uso de tipos de tupla para este propósito. Pero no estoy seguro
es un problema serio.

@isiahmeadows https://github.com/isiahmeadows Veo lo que estás preguntando
sobre, pero ¿por qué el problema es más claro con los parámetros de tipo de descanso?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190667281
.

@isiahmeadows, ¿ puede dar un código de ejemplo para el problema del curry?

Sigo pensando que incluso si usaras parámetros de tipo de descanso (por lo que estoy a favor), tendrías que decidir explícitamente no permitir argumentos en exceso, pero estoy de acuerdo con

@sandersn @JsonFreeman

type FullCurry<T> = ((initial: T, xs: T[]) => T) | ((initial: T) => (xs: T[]) => T)
declare function foldl<T>(func: (acc: T, item: T) => T, initial: T, xs: T[]): T
declare function foldl<T>(func: (acc: T, item: T) => T): FullCurry<T>
declare function foldl<T>(func: (acc: T, item: T) => T, initial: T): (xs: T[]) => T

interface Function<T, R, ...A> {
    apply<U extends T>(inst: U, args: [...A]): R
    apply(inst: T, args: [...A]): R
}

function apply(reducer: (initial: number) => number): (number[]) => number {
    reducer.apply(undefined, [0, []])
}

const func = apply(foldl<number>((x, y) => x + y))

func([1, 2, 3]) // Runtime error

También agregaré mi variación. Veamos el ejemplo de curry variadic de la propuesta:

function curry<...T,...U,V>(f: (...ts: [...T, ...U]) => V, ...as:...T): (...bs:...U) => V {
    return ...b => f(...as, ...b);
}

Entonces, comencé a usarlo:

function f(a: number, b: string, c: string) { return c.toUpperCase(); }
var a: [number, string] = [1, "boo", 2]; // valid
const cf = curry(f, ...a); // cf is of type string => string
cf("a"); // runtime error

@isiahmeadows Ya sea que se representen como parámetros de tipo de reposo o como tipos de tupla, parece que se opone a la capacidad de distribuirlos en una posición de tupla.

@Igorbek Creo que su ejemplo es similar en el sentido de que el problema no es cómo se representan las secuencias de tipos variadic. Es la capacidad de distribuirlos en tuplas lo que genera problemas.

@JsonFreeman Es más que me opongo a este comportamiento:

class A {}
class B {}
class C {}

declare function foo(a: A, b: B): C;

// This should not work
let value: [A, B, C]
foo(...value)

¿Eso aclara?

@isiahmeadows debería funcionar realmente

@JsonFreeman
Siento que no debería. Esa es mi mayor objeción. Siento que es potencialmente peligroso si lo es.

Pregunta: ¿cuál debería ser el tipo de retorno inferido de ret ?

declare function foo(a: A, b: B, c: C, d: D): D
let ret = foo.bind(...[new A(), new B(), new D()])

En realidad, esto es muy importante.

Ese último ejemplo definitivamente parece que no debería funcionar. Esencialmente, necesita un mecanismo para alinear secuencias de tipos si function.bind realmente va a funcionar correctamente. Necesitaría algo parecido a la unificación, donde los tipos de argumentos para enlazar se comparan con los argumentos de la función original, y luego el resto está en el tipo de retorno.

Dicho esto, no parece que nada de lo que se ha propuesto o discutido pueda manejar eso (independientemente de si se permiten argumentos adicionales de tuplas), aunque es posible que me haya perdido algo.

Creo que el mayor problema es que algún tipo de coincidencia de patrones de tupla, donde el tipo de cada parámetro se compara con los tipos de propagación, debe realizarse con los parámetros de tipo (como los argumentos de LiveScript / CoffeeScript) para solucionar ese problema. Probablemente sea imposible de otra manera. Y en cuanto a lo complicado que es, buena suerte implementándolo. :sonrisa:

@JsonFreeman

O para ser más precisos, requerirá una verificación de tipo no estricta (en el sentido de ansioso frente a perezoso) para funcionar. También creo que es probablemente una extensión más útil que los tipos variados, de todos modos, ya que prácticamente abre la puerta a muchas otras cosas más útiles, como los tipos auto-recursivos.

// I hate this idiom.
interface NestedArray<T> extends Array<Nested<T>> {}
type Nested<T> = T | NestedArray<T>

// I would much prefer this, but it requires non-strict type checking.
type Nested<T> = T | Nested<T>[]

Afortunadamente, la verificación de tipos no estricta debería ser un cambio puramente irrefutable en el sentido de que solo el código que anteriormente no se pudo verificar ahora funciona.

Eso es probablemente lo más importante que bloquea la escritura adecuada de Function.prototype.bind , aparte del hecho de que requerirá una firma de tipo muy compleja.

Esa es una conexión interesante. Sin embargo, no estoy convencido de que estén relacionados. El problema de los tipos recursivos es una consecuencia de la política de almacenamiento en caché de los genéricos y la representación de los alias de tipos en el compilador. Toda la información está ahí, es solo el diseño del compilador lo que se interpone.

Para la coincidencia de patrones de tupla, no siempre se puede saber cuántos argumentos se comparan con la tupla. Si extiende una matriz en los argumentos de bind , no sabe cuántos quedan en la devolución de llamada resultante.

@JsonFreeman Dicho esto, ¿cree que, como paso del argumento de adopción, la propuesta del operador de difusión # 6229 debe considerarse primero?

@JsonFreeman

Y la verificación no estricta de tipos permitiría suficiente pereza para facilitar la solución de ese problema con Function.prototype.bind . Con tal pereza, podría lograr ese tipo con lo siguiente (que requerirá una sintaxis de tupla para secuenciarlos, a menos que varios parámetros de descanso estén bien en una declaración de tipo):

interface Function {
    bind<R, T, ...X, ...Y>(
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

¿Por qué esto requeriría una verificación de tipo no estricta para inferir? Debe deducir el tipo de resto paso a paso para verificar la función. Dado lo siguiente, así es como debería verificar:

// Values
declare function func(a: number, b: string, c: boolean, d?: symbol): number

let f = func.bind(null, 1, "foo")

// How to infer
bind<R, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => R,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => R

// Infer first type parameter
bind<number, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => number,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer second type parameter
bind<number, any, ...X, ...Y>(
    this: (this: any, ...args: [...X, ...Y]) => number,
    thisObject: any,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer first part of rest parameter
bind<number, any, number, ...*X, ...Y>(
    this: (this: any, ...args: [number, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, ...*X]
): (this: any, ...rest: [...Y]) => number

// Infer second part of rest parameter
bind<number, any, number, string, ...*X, ...Y>(
    this: (this: any, ...args: [number, string, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, string, ...*X]
): (this: any, ...rest: [...Y]) => number

// First rest parameter ends: all ones that only uses it are fully spread
bind<number, any, number, string, ...Y>(
    this: (this: any, ...args: [number, string, ...Y]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [...Y]) => number

// Infer first part of next rest parameter
bind<number, any, number, string, boolean, ...*Y>(
    this: (this: any, ...args: [number, string, boolean, ...*Y]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, ...*Y]) => number

// Infer second part of next rest parameter
// Note that information about optional parameters are retained.
bind<number, any, number, string, boolean, symbol?, ...*Y>(
    this: (
        this: any,
        ...args: [number, string, boolean, symbol?, ...*Y]
    ) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, symbol?, ...*Y]) => number

// Second rest parameter ends: all ones that only uses it are exhausted
bind<number, any, number, string, boolean, symbol?>(
    this: (this: any, ...args: [number, string, boolean, symbol?]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, symbol?]) => number

// All rest parameters that are tuples get converted to multiple regular
parameters
bind<number, any, number, string, boolean, symbol?>(
    this: (
        this: any,
        x0: number,
        x1: string,
        x2: boolean,
        x3?: symbol
    ) => number,
    thisObject: any,
    x0: number,
    x1: string
): (this: any, x0: boolean, x1?: symbol) => number

// And this checks

Así es como funciona la verificación de tipos no estricta. Infiere tipos según sea necesario, en lugar de en el instante en que los ve. Puede (y debe) combinar las dos pasadas, de modo que los tipos incorrectos fallen. Ejemplo:

let f = func.bind(null, 1, Symbol("oops"))

// How to infer
bind<R, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => R,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => R

// Infer first type parameter
bind<number, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => number,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer second type parameter
bind<number, any, ...X, ...Y>(
    this: (this: any, ...args: [...X, ...Y]) => number,
    thisObject: any,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer first part of rest parameter
bind<number, any, number, ...*X, ...Y>(
    this: (this: any, ...args: [number, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, ...*X]
): (this: any, ...rest: [...Y]) => number

// Infer second part of rest parameter
bind<number, any, number, string, ...*X, ...Y>(
    this: (this: any, ...args: [number, string, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, symbol /* expected string */, ...*X] // fail!
): (this: any, ...rest: [...Y]) => number

En este caso, el parámetro esperado debe ser el primero inferido en esa ronda, haciendo una iteración de profundidad primero. En este caso, el primero que se infirió en esa búsqueda fue una cadena, y los símbolos no se pueden asignar a las cadenas, por lo que esto falla.


Y debido a esto y al intentar escribir Function.prototype.apply , mi opinión sobre el uso de tuplas para aplicar tipos de descanso ha cambiado.

interface Function {
    apply<T, R, ...X>(
        this: (this: T, ...args: [...X]) => R,
        thisArg: T,
        args: [...X]
    ): R
}

Algunas otras notas:

  1. Tiene que haber una forma de distribuir matrices y tuplas como parámetros de tipo resto.

ts interface Foo extends Function<void, ...string[]> {}

  1. Es necesario que haya dos tipos separados para constructores e invocables, siendo las funciones la unión de los dos. Los objetos invocables deben implementar la interfaz invocable, los constructores de clases deben implementar la interfaz construible y las funciones de ES5 deben implementar la unión de los dos.
  2. Function.prototype.bind y los amigos deben verificar todas las sobrecargas de la función. Si hay varios que funcionan, debería devolver una unión de todos ellos.

Sin embargo, esos parámetros de tipo en su ejemplo no son realmente los parámetros de tipo de la firma de enlace. Pertenecen al tipo de función. Pero sí, la idea es que si pudieras usar dos parámetros de resto, o extender dos parámetros de tipo resto en una tupla, podrías escribir esto.

Para que la firma de enlace sea lo suficientemente flexible, el límite entre ...X y ...Y debe decidirse por llamada. Sería necesario inferirlo. Sin embargo, sería un problema si una firma usara ...X de forma aislada. En este caso, el límite no se habrá decidido. Por ejemplo:

interface SomeType<T, R, ...X, ...Y> {
     someMethod(someArgs): [...X]; // No way of knowing how long X is 
}

Y las sobrecargas son un gran problema para el tipo de función. No creo que desee tomar el tipo de unión por elementos en cada argumento, porque eso permitiría mezclar y combinar parámetros entre sobrecargas. ¿Es eso lo que querías decir?

@JsonFreeman

_TL; DR: Salta al salto de línea horizontal. Tengo una idea nueva y más práctica.

  1. Sí, soy consciente de que realmente pertenecen al mismo Function .
  2. Ese tipo de problema es la razón por la que dije que la coincidencia de tipos no estricta (en el sentido de Haskell) es necesaria. No puede resolver con entusiasmo el tipo normalmente, porque eso requeriría una búsqueda iterativa y perezosa para hacer. Es posible determinar algorítmicamente, pero tendría que realizar un seguimiento de las cosas que normalmente no necesitarían rastrear en C ++.
  3. Si los dos argumentos están aislados (como en su ejemplo), el compilador debería quejarse. Y esa situación se puede detectar con un análisis de dependencia a nivel de tipo de cada argumento variable en la interfaz / lo que sea. Tampoco es trivial, pero se puede verificar al leer la declaración de tipo en sí (en realidad, poco después).

Aunque también estoy pensando que podría ser un poco más factible definir este tipo de situaciones solo con el método en cuestión. También será mucho más fácil y rápido detectar esos tipos de problemas potenciales a los que aludiste.

interface Function<R, T, ...A> {
    // Split it up for just this method, since it's being resolved relative to the
    // method itself.
    bind[...A = ...X, ...Y](
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

Hay otro problema potencial que será mucho más difícil de resolver (y por qué creo que debería limitarse a 2, no a _n _ divisiones):

declare function foo<...T>[...T = ...A, ...B, ...C](
    a: [...A, ...C],
    b: [...A, ...B],
    c: [...B, ...C]
): any

// This should obviously check, but it's non-trivial to figure that out.
let x = foo<
    boolean, number, // ...A
    string, symbol,  // ...B
    Object, any[]  // ...C
>(
    [true, 1, {}, []],
    [true, 1, "hi", Symbol()],
    ["hi", Symbol(), {}, []]
)

_Lo siento si estoy profundizando demasiado en la teoría de CS aquí ..._

Sí, creo que es la idea correcta. No es bonito, pero no puedo pensar en ninguna otra forma de escribir correctamente bind , conociendo los argumentos de tipo de Function . Lo último es que se debe inferir un límite. Y estoy de acuerdo en que debería limitarse a 2 cubos para que tenga que inferir 1 límite, en lugar de un número arbitrario de límites, que puede explotar combinatoriamente.

Es probable que haya más problemas en los que no hemos pensado.

@JsonFreeman Otro problema son cosas como curry . Todavía tengo que encontrar algo que pueda escribir eso correctamente. Y tomará un tiempo antes de que pueda. Tendría que hacer una piratería al estilo Haskell para llegar a un proceso de este tipo.

Pensando en cómo podría funcionar el tipo de propuesta con algunas funciones de Bluebird.

interface PromiseConstructor {
    // all same type
    all<T>(promises: PromiseLike<T>[]):  Promise<T[]>;
    join<T>(...promises: PromiseLike<T>[]):  Promise<T[]>;
    // varying types
    all<...T>(promises: [...PromiseLike<T>]): Promise<[...T]>;
    join<...T>(...promises: [...PromiseLike<T>]): Promise<[...T]>;
    // this is sketchy...    ^
}

interface Promise<T> {
    // all same type
    then<U>(onFulfill: (values: T) => U): Promise<U>;
    spread<U>(onFulfill: (...values: T) => U): Promise<U>;
}
interface Promise<...T> {
    // varying types
    then<U>(onFulfill: (values: [...T]) => U): Promise<U>;
    spread<U>(onFulfill: (...values: [...T]) => U): Promise<U>;
}

¿Tenemos una solución para el all<...T>(promises: [...PromiseLike<T>]): Promise<...T>; anterior?

@DerFlatulator

Vea mi gran comentario en PromiseConstructor. También corrigí la interfaz de tu Promesa para que esté un poco más cerca de mi propuesta.

interface PromiseConstructor {
    new <T>(callback: (
        resolve:
        (thenableOrResult?: T | PromiseLike<T>) => void,
        reject: (error: any) => void
    ) => void): Promise<T, [T]>;
    new <...T>(callback: (
        resolve:
        (thenableOrResult?: [...T] | PromiseLike<[...T]>) => void,
        reject: (error: any) => void
    ) => void): Promise<[...T], ...T>;

    // all same type
    all<T>(promises: PromiseLike<T>[]):  Promise<T[], ...T[]>;
    join<T>(...promises: PromiseLike<T>[]):  Promise<T[], ...T[]>;

    // varying types
    all<...T>(promises: [...PromiseLike<T>]): Promise<[...T], ...T>;
    join<...T>(...promises: [...PromiseLike<T>]): Promise<[...T], ...T>;

    // all<...T>(promises: [...PromiseLike<T>]): Promise<[...T], ...T> should
    // expand to this:
    //
    // all<T1, T2, /* ... */>(promises: [
    //     PromiseLike<T1>,
    //     PromiseLike<T2>,
    //     /* ... */
    // ]): Promise<[T1, T2, /* ... */], T1, T2, /* ... */>;
    //
    // This should hold for all rest parameters, potentially expanding
    // exponentially like ...Promise<[Set<T>], ...Thenable<T>> which should
    // expand to something like this:
    //
    // Promise<[Set<T1>], Thenable<T1>, Thenable<T2> /* ... */>,
    // Promise<[Set<T2>], Thenable<T1>, Thenable<T2> /* ... */>,
    // // etc...
}

interface Promise<T, ...U> {
    // all same type
    then<V>(onFulfill: (values: T) => V): Promise<[V], V>;
    spread<V>(onFulfill: (...values: T) => V): Promise<[V], V>;

    // all same type, returns tuple
    then<...V>(onFulfill: (values: T) => [...V]): Promise<[...V], ...V>;
    spread<...V>(onFulfill: (...values: T) => [...V]): Promise<[...V], ...V>;

    // varying types
    then<V>(onFulfill: (values: [...U]) => V): Promise<[V], V>;
    spread<V>(onFulfill: (...values: [...U]) => V): Promise<[V], V>;

    // varying types, returns tuple
    then<...V>(onFulfill: (values: [...U]) => [...V]): Promise<[V], ...V>;
    spread<...V>(onFulfill: (...values: [...U]) => [...V]): Promise<[V], ...V>;
}

Si [...Foo<T>] expande a [Foo<T1>, Foo<T2>, /*... Foo<TN>*/] , ¿entonces [...Foo<T,U>] un error de sintaxis o una expansión combinatoria?

@DerFlatulator

  1. Si exactamente uno de T o U es un parámetro de descanso, se expande normalmente. Suponiendo que T es un parámetro de descanso, entonces sería [Foo<T1, U>, Foo<T2, U>, /*... Foo<TN, U>*/] .
  2. Si ambos son parámetros de reposo, y su longitud puede inferirse correctamente, debería ser una expansión combinatoria (bueno ... la longitud de T multiplicada por la longitud de U).
  3. Si ninguno de los dos son parámetros de descanso, es un error de sintaxis.

Tenga en cuenta que me opongo firmemente a más de 2 parámetros de descanso por razones prácticas, y que los parámetros de descanso, si es necesario dividirlos, solo deben dividirse por método. Algo como esto:

interface Function<R, T, ...A> {
    // Split it up for just this method, since it's being resolved relative to the
    // method itself.
    bind[...A = ...X, ...Y](
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

_ (Si alguien puede encontrar una mejor sintaxis, soy todo oídos. No me gusta, pero no puedo encontrar nada que no entre en conflicto visualmente) _

@isiahmeadows

Con 2., ¿en qué orden sería la expansión?

[
Foo<T1, U1>, Foo<T2, U1>, /*... */ Foo<TN,U1>,
Foo<T1, U2>, Foo<T2, U2>, /*... */ Foo<TN,U2>,
/* ... */
Foo<T1, UN>, Foo<T2, UN>, /*... */ Foo<TN,UN>
]

O al revés:

[
Foo<T1, U1>, Foo<T1, U2>, /*... */ Foo<T1,UN>,
Foo<T2, U1>, Foo<T2, U2>, /*... */ Foo<T2,UN>,
/* ... */
Foo<TN, U1>, Foo<TN, U2>, /*... */ Foo<TN,UN>
]

¿No causaría confusión esta ambigüedad? Quizás sería prudente limitarse a una dimensión.


Solo una sugerencia alternativa para la sintaxis dividida:

interface Function<R, T, ...A> {
    bind<[...X, ...Y] = [...A]>(
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

@DerFlatulator

Esperaría el segundo. Y dudo que cause demasiada confusión, ya que siempre que sea así, la gente se acostumbrará rápidamente. También es un caso marginal inusual que solo lo encontrarán en la práctica personas que saben lo que están haciendo, o personas que deberían cuestionar la necesidad en primer lugar.

También lo estoy viendo mientras expande el primero, luego el segundo para cada parte del primero. Como este pseudocódigo:

for (let TT of T) {
  for (let UU of U) {
    expand(TT, UU);
  }
}

Repitiendo algunas de las ideas anteriores ...

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TNewThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>
}

Aquí, [...TBound, ...TUnbound] = [...TArgs] es válido porque la longitud de ...TBound se conoce a partir de la longitud de args . También permite cambiar el tipo de TThis .

Un problema con este enfoque es que solo puede vincular this una vez, por ejemplo:

interface IFoo { a: number }
interface IBar extends IFoo { b: boolean }
function f(a: number) { }

let x = f.bind(<IBar>{ a: 1, b: false }, 2); // inferred type: Function<number, IBar>
let y = x.bind(<IFoo>{ a: 1 }) // inferred type: Function<number, IFoo>

El tipo inferido de y es incorrecto, debería ser Function<number, IBar> . No estoy seguro de si esto es una preocupación o no, pero resolverlo requeriría introducir lógica en la sintaxis <T> .

Opción 1

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TNewThis = TThis is undefined ? TNewThis : TThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>;
}

opcion 2

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TThis is undefined,
        TNewThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>;

    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TThis is defined
    >(
        thisObject: any,
        ...args: [...TBound]
    ): Function<TReturn, TThis, ...TUnbound>;
}

Sin embargo, eso probablemente estaría fuera del alcance de esta propuesta.

No creo que debamos permitir este tipo de expansiones mediante el uso del operador de propagación de tipos. Pienso en el operador de extensión como un "eliminador de corchetes" que está absolutamente alineado con el operador de extensión de matriz y el operador de extensión de objeto / propiedades (propuesta de etapa 2). Simplemente compare:

let a =        [1, 2];
let b = [0, ...a     , 3];
//      [0, ...[1, 2], 3]
//      [0,     1, 2 , 3]  // removed brackets

let c =               { a: 1, b: "b" };
let d = { e: true, ...c               , f: 3 };
//      { e: true, ...{ a: 1, b: "b" }, f: 3 };
//      { e: true,      a: 1, b: "b"  , f: 3 };

Está sugiriendo extenderlo para construir un nuevo conjunto de tipos:

<...T> = <A, B, C>
...U<T> = <U<A>, U<B>, U<C>>

Es una operación completamente diferente. Si lo desea, podría modelarse mediante construcciones de orden superior como:

<...(from R in T select U<R>)> // linq-like
<...(T[R] -> U<R>)> // ugly

@Igorbek ¿Qué tal usar un operador para determinar qué se expandirá?

interface PromiseConstructor {
    all<
      ...T, 
      [...TThen] = ...(PromiseLike<@T> | @T)
    >(
      promises: [...TThen]
    ): Promise<[...T], ...T>;
}

Donde ...Foo<<strong i="9">@T</strong>, U> expande a [Foo<T1,U>, /*...*/, Foo<TN,U>] .

...(PromiseLike<@T> | @T) expande a
[PromiseLike<T1>|T1, /*...*/, PromiseLike<TN>|TN]

Algunas alternativas de sintaxis:

  • ...Foo<&T,U>
  • (T) Foo<T,U>
  • (...T => Foo<T,U>)
  • for (T of ...T) Foo<T,U>

Estoy de acuerdo con @Igorbek aquí. Al menos en esta etapa, el mapeo de secuencias de tipos no parece una prioridad, dado que todavía estamos tratando de resolver el problema más básico de los parámetros de tipos variadic.

No tengo mucho problema con prohibirlo (al menos inicialmente), ya que el comportamiento para eso es bastante poco intuitivo, y dos personas diferentes podrían esperar dos cosas muy diferentes, incluso. Estoy de acuerdo con @Igorbek al menos por ahora, ya que TypeScript primero necesita tener un modelo de tipo de orden superior (eso es map ping un tipo en cierto sentido). Y los tipos de orden superior no son exactamente algo a lo que simplemente se pueda atornillar.

Así que definitivamente: +1: por prohibir eso, probablemente durante bastante tiempo. Aunque es bueno tenerlo, es muy complicado de implementar y sería un truco completo, ya que TypeScript no usa un sistema de tipos funcional y seguro.

Llego un poco tarde, pero también estoy de acuerdo con

Empaquetar tipos en una tupla parece coherente con el uso que hace TypeScript del operador de propagación:

let [x, y, ...rest] = [1, 2, 3, 4, 5] // pack
foo(...params) // unpack
let all = [1, 2, ...other, 5] // unpack

// keep in mind this is already implemented, which kind of similar to mapping types
function map(arr) { ... }
let spreadingmap = [1, 2, ...map(other), 5];

Lo que hace que <...T_values> = [T1, T2, T3, etc...] sea ​​mucho más fácil de razonar.

Mientras que C ++ usa el operador de extensión para empaquetar y puntos suspensivos para desempaquetar, usar extensión para ambos es más consistente con Typecript.

module Promise {
  function all<...T_values>(   // pack into a tuple of types, conceptually identical to rest parameters
      values: [ (<PromiseLike<T*>> ...T_values) ]  // unpack, cast, then repack to tuple
  ): Promise<T_values> // keep it packed since T_values is a tuple of whatever types
}

@isiahmeadows @JsonFreeman ¿cuál sería el punto de todo esto sin mapeo?

También como se planteó en # 1336, ¿qué tal una variadic Array.flatten ?

@jameskeane Esa primera mitad fue la idea inicial, pero no cubre el caso de un parámetro de descanso intermedio (que tienen algunas API):

function foo<...T>(a: Foo, b: Bar, ...rest: [...T, Baz]): Foo;

Tampoco cubre muy bien Function.prototype.apply vs Function.prototype.call .

En cuanto al # 1336, podría implementarse de manera similar a través de esto:

angular.module('app').controller(['$scope', function($scope: ng.IScope) { /*etc...*/ }]);

interface IModule {
  controller(injectable: [...string[], () => any]);
}

Me he puesto al día y me di cuenta de que estaba asumiendo ingenuamente que los tipos de tupla tenían una longitud estricta; cuál imo es el más intuitivo. Entonces, asumiendo que obtenemos tipos de tuplas de longitud estricta (# 6229), ¿cuáles son los problemas?

@isiahmeadows En su ejemplo anterior del caso del parámetro de descanso medio, ¿no se resuelve teniendo tuplas de longitud estrictas? Estoy leyendo ...rest: [...T, Baz] lo mismo que desempaquetar el spread arr = [...other, 123] . Este es el mismo problema que planteó con curry , ¿verdad?

En cuanto a apply y call , ¿no están cubiertos por tipos que se cruzan? (No es que realmente vea el valor de tener los tipos en la interfaz Function todos modos).

// as in
const t: [any, string] & [number, any] = [1, "foo"]

interface Function<R, T, ...A> {
    bind<...Y, ...Z>(
        this: (this: T, ...args: A & [...Y, ...Z]) => R, // tricky bit, luckily intersecting tuples is pretty easy
        thisObject: T,
        ...args: Y
    ): (this: any, ...rest: Z) => R
}

@jameskeane

La propuesta variada actual asume que # 6229 realmente termina siendo aceptado (es decir, las tuplas son estrictas por defecto).

En cuanto a func.apply , func.bind , func.call y _.curry , el único problema es con func.bind , _.curry , y amigos, o más generalmente cualquier cosa que use una aplicación parcial. También debe poder elegir qué parámetro de descanso separar, y realmente solo se puede hacer por método.

call y apply son bastante sencillos:

type Callable<R, T, ...A> = (this: T, ...args: [...A]) => R;

interface Function<R, T, ...A> {
    call(this: Callable<R, T, ...A>, thisArg: T, ...args: [...A]): R;
    apply(this: Callable<R, T, ...A>, thisArg: T, args: [...A]): R;
}

bind sería más difícil. Los parámetros divididos tendrían que coincidir según sea necesario, a diferencia de lo que ocurre con entusiasmo, como es el caso ahora, hasta que la primera mitad dividida esté completamente descomprimida. Esto debe implementarse como sintaxis, para que el compilador pueda diferenciarlo y discernir correctamente el tipo sin evaluar nada.

// Function.prototype.bind
type Callable<R, T, ...A> = (this: T, ...args: [...A]) => R;
type Constructible<R, ...A> = new (...args: [...A]) => R;

interface Function<R, T, ...A> {
    // my proposed syntax for splitting a rest parameter
    bind[[...A] = [...X, ...Y]](
        this: Callable<R, T, ...A>
        thisArg: T,
        ...args: [...X]
    ): Callable<R, any, ...Y>;

    bind[[...A] = [...X, ...Y]](
        this: Constructible<R, ...A>
        thisArg: T,
        ...args: [...X]
    ): Constructible<R, ...Y>;

    bind[[...A] = [...X, ...Y]](
        this: Callable<R, T, ...A> & Constructible<R, ...A>
        thisArg: T,
        ...args: [...X]
    ): Callable<R, T, ...Y> & Constructible<R, ...Y>;
}

curry sería extremadamente difícil, ya que tiene que saber que f(1, 2, 3) === f(1, 2)(3) === f(1)(2, 3) === f(1)(2)(3) . No solo tiene que haber la capacidad de dividir un parámetro de descanso en dos como en bind , tiene que haber la capacidad de realizar una coincidencia de patrones muy primitiva por método.

interface Curried<R, T, ...XS> {
    // none passed
    (): this;

    // all passed
    (this: T, ...args: [...XS]): R;
}

interface CurriedMany<R, T, X, ...YS> extends Curried<R, T, X, ...YS>  {
    // penultimate case, constraint that ...YS contains no parameters
    [[...YS] = []](arg: X): Curried<R, T, X>;

    // otherwise, split rest into ...AS and ...BS, with `A` used as the pivot
    // (basically, default case)
    [[...YS] = [...AS, A, ...BS]](
        ...args: [X, ...AS]
    ): CurriedMany<R, T, A, ...BS>;
}

function curry<R, T>(f: (this: T) => R): (this: T) => R;
function curry<R, T, X>(f: (this: T, arg: X) => R): Curried<R, T, A>;
function curry<R, T, X, ...YS>(
    f: (this: T, arg: X, ...args: [...YS]) => R
): CurriedMany<R, T, X, ...YS>;

No creo que las adiciones de curry lo hagan Turing completo, pero estaría cerca. Creo que lo principal que lo evitaría sería la capacidad de igualar especializaciones de un tipo en particular (que C ++, Scala y Haskell, tres lenguajes con sistemas de tipo Turing-complete, tienen todos).

@sandersn No pude ver un ejemplo anterior, pero ¿puedo preguntar acerca de las restricciones en los parámetros variadic?

Considere el siguiente ejemplo:

interface HasKey<T> {
    Key(): T;
}

class Row<...T extends HasKey<X>, X> {
    // ...
}

_Por cierto, consulte https://github.com/Microsoft/TypeScript/issues/7848 para ver una discusión sobre la posibilidad de eliminar el requisito de que X debe figurar en la lista_

Ahora, existe potencialmente alguna ambigüedad aquí en cuanto a si la restricción es:

  1. (...T) extends HasKey<X> o
  2. ...(T extends HasKey<X>)

En este ejemplo, supongo que 2.

¿Serán posibles este tipo de restricciones (1 y / o 2)?

@myitcv 2 probablemente sería la mejor ruta a seguir, pero tendría sentido simplemente reutilizar la lógica existente para verificar la restricción.

Bueno ... acabo de darme cuenta de algo: ¿cómo serían las matrices que contienen tipos variadic? O más específicamente, ¿qué tipo es arg continuación?

function processItems<...T>(...args: [...T]): void {
    for (const arg of args) { // Here
        process(arg);
    }
}

Supongo que está preguntando cuál es el tipo de elemento de args . Para las tuplas, creo que este suele ser el tipo de unión de los elementos. No puedo pensar en una mejor manera de escribir eso.

@sandersn, ¿puedes comentar cuál es el estado de esta función? Siento que ha habido mucha discusión, pero no parece que haya un plan definido para la función, ¿es correcto?

@JsonFreeman Estaba preguntando específicamente qué era arg . En mi opinión, debería ser any para mi ejemplo original, y Item<T> a continuación (con la T delimitada por F):

function processItems<...T extends Item<T>>(...args: [...T]): void {
    for (const arg of args) { // Here
        process(arg);
    }
}

Esto es para que los tipos se puedan resolver localmente. No conoce los tipos de antemano, y esto acelerará enormemente la compilación, ya que no tiene que calcular los tipos dentro de la función para cada llamada. Tenga en cuenta que si solo necesita el tipo de un solo argumento, typeof arg será suficiente y probablemente sea más corto.

Oh, lo siento, para el ejemplo original, quise decir que el tipo debería ser T . En realidad, para su segundo ejemplo, también creo que debería ser T.

Quise decir Item<any> en el segundo ... Lo siento.

Cuando dije que debería ser T, estaba asumiendo que T es un tipo, pero supongo que el objetivo de esta característica es que T no es un tipo (creo). Así que sí, supongo que debería ser any y Item<any> en tus ejemplos.

Pero en términos más generales, tengo curiosidad por saber cuán activamente el equipo está considerando esta característica para después de 2.0. No tengo una opinión fuerte, solo me pregunto.

La razón por la que no creo que deba ser necesariamente T es porque no sabes qué es T . A menos que, por supuesto, quiera decir que la variable T represente un solo tipo de la lista de tipos variadic, o cuando se extienda, la lista en sí, es decir, T es el subtipo de todos los argumentos pasados ​​a el argumento ...T , y [...T] se puede asignar a T[] .

O, para aclarar lo que quiero decir con toda la jerga poco clara, esto es lo que quiero decir en términos de código:

// To put it into code
function foo<...T>(list: [...T]): void {
    // This is allowed
    let xs: T[] = list

    // This is allowed
    let list2: [...T] = list

    // This is not allowed
    let list1: [...T] = xs

    // This is allowed
    let item: ?T = null

    // This is not allowed, since it's not immediately initialized
    let other: T

    for (let arg of args) {
        // This is allowed
        let alias: T = arg

        // This is allowed
        let other: ?T = arg

        // This is allowed, since `item` is defined upwards as `?T`
        item = arg

        // This is allowed, since you're doing an unsafe cast from `?T` to `T`.
        alias = item as T
    }
}

Sin embargo, eso probablemente tendría más sentido y sería mucho más flexible.

Todavía está en nuestra lista de cosas interesantes, pero es principalmente de interés para los autores de bibliotecas y tiene una solución decente, _n_ sobrecargas, por lo que no estoy trabajando activamente en ello. Si tuviera que adivinar, diría que 2.1 es posible pero no probable.

Si / cuando nos comprometemos a admitir correctamente el reposo / propagación de objetos (# 2103), entonces los tipos variadic podrían estar lo suficientemente cerca como para extender los tipos y justificar hacerlo todos a la vez. (Los tipos de propagación son una variante de los tipos de objetos que se parecen a { ...T, x: number, ...U, y: string, ...V } ).

Solo quiero mencionar que la solución alternativa n overloads no funciona para clases o interfaces, que es mi interés particular en esta función.

@sandersn ¿Se bind , apply y call en funciones, usando this escribiendo? Creo que sería un compromiso temporal aceptable para muchos y podría detectar bastantes errores en el proceso para algunos proyectos.

@isiahmeadows

La razón por la que no creo que deba ser necesariamente T es porque no sabes qué es T.

Me pareció que había acuerdo en que T es un tipo de tupla de los tipos variádicos. En su ejemplo original, el tipo de arg sería el mismo que el tipo de elemento de tupla (como señaló @JsonFreeman , "el tipo de unión de los elementos"): Por ahora, imagine que el mecanografiado admite el uso de una tupla como descanso tipo (# 5331).

function processItems<...T>(...args: T): void {
  for (const arg of args) { // Here - arg:number|string|boolean
    const other: ??? = arg; // I think the issue is, how to _represent_ this type?
  }
}
processItems(1, 'foo', false); // T is tuple [number, string, boolean]

Aparte de esta propuesta, creo que debería haber una forma de representar el 'tipo de elemento' de una tupla. Ese podría ser otro uso del spread, es decir, como arriba ...T :: number|string|boolean ; que la difusión del tipo de tupla da como resultado su tipo de elemento.

for (const arg of args) {
  const cst: ...T = arg;
}

// also, even without variadic types...
type Record = [number, string];
function foo(args: Record) {
  for (const arg in args) {
    const cst: ...Record = arg;
  }
}

Con esto en mente, sus otros ejemplos:

function foo<...T>(...list: T): void {
  let xs: T[] = [list, list] // array of the variadic tuple type

  // This is allowed
  let list5: (...T)[] = [...list]

  // This is *not* allowed
  let list2: [...T] = list

  // This is not allowed
  let list1: [...T] = xs

  // This **is** allowed
  // single element tuple, of variadic union
  // i.e. with number|string|boolean
  //      list4 = [1] or list4 = ['foo'] or list4 = [false]
  let list4: [...T] = [list[n]]

  // This **is**  allowed
  let other: T;

  // This is allowed
  let another: ...T;

  for (let arg of args) {
    another = arg; // allowed, if spreading the tuple is the union type

  }
}

Sin perder de vista mi objetivo original, quería un Promise.all fuertemente tipado ...

declare module Promise {
  function all<...T>(promises: Promise<...T>[]): T; // means promises is an array of promises to the union type, not what I wanted.

  // Then we need something like, which is now very confusing
  function all<...T>(promises: [...Promise<T*>]): T; 
}}

@sandersn Ahora que otras características solicitadas comienzan a depender de esto, ¿podría aumentar la prioridad? bind escritura de call etc. depende de esto, y la sintaxis de enlace de ES si / cuando sale depende de eso, por lo que ahora hay más cosas en juego que los autores de bibliotecas extravagantes que lo regañan todo el tiempo . :)

No es que esto sea particularmente constructivo de agregar, pero estaría tan feliz si estas dos características se convirtieran en 2.1. Conozco al menos una biblioteca (RxJS), donde no solo la base de código en sí mejoraría con estas características, sino que consumir código también sería significativamente menos incómodo y propenso a errores (cada tercera persona que comienza con Angular 2 es mordida por importaciones faltantes para los operadores que están parcheados en el prototipo observable). Realmente sería una característica revolucionaria para las personas que buscan escribir código funcional mantenible.

¿Podría usarse esto para proporcionar una definición de tipo completa para _.extend , cuyo tipo de retorno es la intersección de todos sus parámetros?

declare module underscore {
  function extend<A, B, C, D, ...>(a: A, b: B, c: C, d: D, ...): A&B&C&D&...;
}

No como está. Necesita una extensión de la propuesta que brinde detalles sobre un nuevo operador para tipos variados, probablemente llamado &. @kitsonk propuso este operador anteriormente, en este comentario .
En este momento, esta característica se encuentra debajo de un par de otras cosas más importantes de inmediato, por lo que no he visto esta propuesta en un tiempo.

Si bien no ofrece tipos variados completos, # 10727 es parte de la solución (y es probable que aborde los desafíos que tenemos (@dojo)).

¡Qué bueno escuchar! Aunque todavía no se trata de tipos variados. :( Por ejemplo, esta semana cuando intenté escribir Object.assign , llegué hasta aquí:

interface Object {
  // binary version
  assign<T,U>(target: T, source: U): { ...T, ...U };
  // variadic version: bind a variadic kind variable ...T
  // and then spread it using SIX dots
  assign<...T>(...targets: ...T): { ......T };
}

Tenga en cuenta que la sintaxis de "seis puntos" es la extensión del objeto de una variable tipo tupla, que no hemos discutido anteriormente.

@sandersn

Con Object.assign en particular, podría escribirse de esta manera, y técnicamente capturar un subconjunto (aunque un poco inferiblemente débil), ya que muta su objetivo (tendría que tener un punto de referencia para esto):

assign<T>(target: T, ...sources: Partial<T>[]): T;

El problema con eso es que muta su objetivo, cambiando su tipo estructural en su lugar.

@isiahmeadows entonces la inferencia arreglaría T para que sea el tipo de target sin los tipos de contabilidad de sources . Puedes probarlo ahora con la versión no variada:

declare function _assign<T>(target: T, source: Partial<T>): T;
_assign({}, { a: 10 }); // T is {}

Como ya se mencionó, assign usa _un tipo de propagación_ # 10727 y se puede definir de esta manera:

// non variadic
declare const assign: {
  <T>(target: T): T;
  <T, S>(target: T, source: S): {...T, ...S};
  <T, S1, S2>(target: T, source1: S1, source2: S2): {...T, ...S1, ...S2};
};
// variadic
declare function assign<T, [...S]>(target: T, ...sources: [...S]): {...T, ...[...S]};

_Nota: sigo insistiendo en la sintaxis basada en tuplas [...T] que tiene mucho más sentido para mí.

@sandersn Por cierto, ¿hay alguna actualización sobre cuándo se aterrizarán los tipos variados? ¿Hay alguna posibilidad de verlo en 2.2?
Y, con respecto a la sintaxis, ¿todavía acepta comentarios sobre la sintaxis o todos están de acuerdo con eso?

La sintaxis y la semántica de bajo nivel aún no tienen un consenso claro.

El martes 13 de diciembre de 2016 a las 13:26, Igor Oleinikov [email protected] escribió:

@sandersn https://github.com/sandersn Por cierto, ¿hay alguna actualización sobre cuándo
¿Se desembarcarán tipos variados? ¿Hay alguna posibilidad de verlo en 2.2?
Y, con respecto a la sintaxis, ¿todavía acepta comentarios sobre la sintaxis o
¿Están todos de acuerdo con eso?

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-266819647 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AERrBIa5fE8PSk-33w3ToFqHD9MCFoRWks5rHuM5gaJpZM4GYYfH
.

¿Alguna idea sobre cuál es el estado de este problema?

¿Cuáles son las opciones en las que está pensando? ¿Está esto en la agenda del equipo? es la única parte débil del sistema de tipos con el que me encuentro repetidamente. tengo dos casos de uso. uno simple y uno complejo, pero más general.

simple sería agregar un supertipo Tuple extends any[] que solo puede ser subtipificado por tipos de tupla. Como los diferenciales deben ser subtipos any[] , esto funcionaría:

declare interface Plugin<A: Tuple, P> {
  (...args: A): P | Promise<P>
}

const p: Plugin<[string, { verbose: boolean }], int> =
  (dest, { verbose = false }) => 4

por el momento, ...args: T[] solo se permite al final de las firmas.

el caso de uso complejo necesitaría ...args: Tuple para ser legal en cualquier lugar dentro de una firma (lo cual no es un problema, ya que las tuplas son de longitud fija):

/**
 * Takes a function with callback and transforms it into one returning a promise
 * f(...args, cb: (err, ...data) => void) => void
 * becomes
 * g(...args) => Promise<[...data]>
 */
function promisify<A extends Tuple, D extends Tuple, E>
    (wrapped: (...args: A, cb: (error: E, ...data: D) => void) => void)
    : ((...args: A) => Promise<Data>) {
  return (...args) => new Promise((resolve, reject) =>
    wrapped(...args, (e, ...data) =>
      e ? reject(e) : resolve(data)))
}

const write: ((fd: number, string: string, position?: number, encoding?: string)
              => Promise<[number, string]>) =
  promisify(fs.write)

Sí, recién comencé con TypeScript ayer y esto ya ha hecho que sea imposible escribir automáticamente mis funciones (todavía puedo hacerlo manualmente, por supuesto) debido a un solo decorador que estoy usando para ajustar mis funciones (que es lo primero ¡Intenté comenzar con!):

function portable(func) {
    return function(...args) {
        if (this === undefined) {
            return func(...args)
        } else {
            return func(this, ...args)
        }
    }
}

Efectivamente, todo lo que hace el decorador es permitir que se pueda llamar a una función como un método para que se puedan adjuntar como un método y funcionen de manera idéntica, como ejemplo básico, aquí hay un mal ejemplo que parchea el prototipo Array con una versión básica de flatMap :

function _flatMap<T, R>(
    array: T[],
    iteratee: (item: T) => R[]
): R[] {
    let result: R[] = []
    for (const item of array) {
        for (const value of iteratee(item)) {
            result.push(value)
        }
    }
    return result
}

const flatMap = portable(_flatMap)
Array.prototype.flatMap = flatMap

flatMap([1,2,3,4], x => [x, x])
// Is the same as
[1,2,3,4].flatMap(x => [x, x])
// Is the same as
flatMap.apply([1,2,3,4], [x => [x, x]])
// Is the same as
flatMap.call([1,2,3,4], x => [x, x])

Ahora, con suerte, es obvio que el tipo de flatMap (no _flatMap ) es:

function flatMap<T, R>(this: T[], iteratee: (item: T) => R[]): R[]
function flatMap<T, R>(this: undefined, array: T[], iteratee: (item: T) => R[]): R[]

Sin embargo, no tengo forma de agregar types a portátil porque no puedo extraer el tipo de parámetros de _flatMap para luego usarlo dentro de la definición de tipo de la función decorada, imagino que con esta propuesta podría escribe algo como:

// First argument to func is required for portable to even make sense
function portable<T, R, ...Params>(func: (first: T, ...rest: Params) => R) {
    // The arguments of calling with this is undefined should be simply
    // exactly the same as the input function
    function result(this: undefined, first: T, ...rest: Params): R
    // However when this is of the type of the first argument then the type
    // should be that the parameters are simply the type of the remaining
    // arguments
    function result(this: T, ...rest: Params): R
    function result(...args) {
        if (this === undefined) {
            return func(...args)
        } else {
            return func(this, ...args)
        }
    }
    return result
}

Solo quería compartir esto, ya que muestra mis experiencias iniciales con TypeScript y tal vez muestra otro caso de por qué los genéricos variadic son importantes.

@sandersn :

tiene una solución decente: n sobrecargas

Si bien técnicamente no es incorrecto, creo que esto no refleja completamente la realidad aquí. Sí, técnicamente la falta de esto no pudo evitar que las sobrecargas escribieran cualquier función mencionada en este hilo; y, sin embargo, este pequeño inconveniente ha significado que ninguna de estas soluciones basadas en sobrecarga se haya convertido en lib.d.ts hasta ahora.

Y de hecho, muchos en este hilo se han sentido lo suficientemente desesperados al abordar sus respectivas funciones como para proponer aún más sintaxis que originalmente no formaba parte de esta propuesta, incluido su ...... , así como ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> y <PromiseLike<T*>> .

Creo que esto demuestra que todos estamos tratando de abordar los problemas aquí, compartimos la sensación general de que necesitamos una sintaxis más poderosa como esta y esperamos que cualquier camino que elijamos aquí nos ayude a resolverlos.

Nota al margen: para R.path de Ramda, habíamos generado un tipeo de miles de líneas de sobrecargas, que aún perdían el soporte de tuplas (las permutaciones habrían explotado mucho más aún), y solo causaron que la compilación en proyectos reales no terminara ya no. La iteración recientemente descubierta como una alternativa aparentemente viable allí (# 12290).

Por cierto, afaik todavía tenías que comentar sobre la propuesta presentada por @Artazor e @Igorbek. ¿Qué pensabas de eso?

Me gustaría discutir con una implementación básica como esa aquí (más el n. ° 6606), podemos hacer casi cualquier cosa. Ofreceré un par de soluciones aquí para ilustrar esto, pero estoy abierto a más preguntas.

Permítanme primero repasar algunos lugares donde se podría implementar un operador ... :

v ... para | definición (captura) | usar (difundir)
- | - | -
función | type Fn = (...args: any[]) => {} | type Returns = typeof fn(...MyTuple); (# 6606)
matriz | desestructuración de tuplas a nivel de tipo. técnicamente se puede emular usando acceso de índice + propagación (ver a la derecha) + recursividad. | type Arr = [Head, ...Tail];
objeto | Desestructuración de objetos a nivel de tipo. no es necesario, solo use Omit , vea # 12215. | type Obj = { a: a, ...restObj }; (no es necesario, igual que Overwrite , ver # 12215)
genéricos | definir type Foo<...T> para hacer Foo<1, 2, 3> (captura [1, 2, 3 ] en T ). divertido, pero no sé qué caso de uso requiere esto. | defina type Bar<A,B,C> para hacer Bar<...[1,2,3]> ( A = 1 etc.). ídem, no conozco casos de uso que necesiten esto.
sindicatos (bonificación) | ? | type Union = "a" | "b"; type MyTuple = ...Union; // ["a", "b"] (el pedido no es confiable pero permite la iteración de uniones / objetos a través de tuplas. De todos modos, muy fuera del alcance aquí).

Así que solo hay dos instancias ... que son inmediatamente relevantes; específicamente, los dos usados ​​aquí:

declare function f<U, T>(head: U, ...tail: T): [U, ...T];

En el contexto de # 6606, otro se vuelve relevante: la capacidad de descomprimir un tipo de tupla para la aplicación de una función, por ejemplo, typeof f(...MyTuple) . Creo que esto es suficiente para resolver los problemas más difíciles de los que he oído mencionar aquí. Para intentar ofrecer algunas soluciones aquí:

@jameskeane :

Creo que debería haber una forma de representar el 'tipo de elemento' de una tupla

Si desea obtener la unión de sus elementos, consulte mi TupleToUnion .

Promise.all

// helpers: `mapTuple` needs #5453 to define, #6606 to use
type TupleHasIndex<Arr extends any[], I extends number> = ({[K in keyof Arr]: '1' } & Array<'0'>)[I];
type Inc = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // longer version in gist
declare function mapTuple<F extends (v: T) => any, Tpl extends T[], T>(f: F, tpl: Tpl): MapFn<F, Tpl, T>;
type MapFn<
    F extends (v: T) => any,
    Tpl extends T[],
    T,
    // if empty tuple allowed:
    // I extends number = 0,
    // Acc = []
    // otherwise:
    I extends number = 1,
    Acc = [F(Tpl[0])]
> = { 1: MapFn<F, Tpl, T, Inc[I], [...Acc, F(Tpl[I])]>; 0: Acc; }[TupleHasIndex<Tpl, Int>];

declare module Promise {
  function all<Promises extends Promise<any>[]>(promises: Promises): typeof mapTuple(<T>(prom: Promise<T>) => T, Promises);
}

@danvk :

_.extend

@sandersn :

Object.assign

Ambas son solo versiones variadas de mergeAll de Ramda. ¡No se necesitan seis puntos!

@isiahmeadows :

¿Alguna idea sobre cuál es el estado de este problema?
La sintaxis y la semántica de bajo nivel aún no tienen un consenso claro.

Si entiendo correctamente, a usted principalmente le preocupaba si el enfoque ofrecido por algunos de los otros tomaría en cuenta abordar tipos más difíciles, como curry y bind que mencionó. Aquí está mi opinión sobre ese en particular, siguiendo su propuesta.
La estrategia es un poco similar, engañando el hecho de que es difícil de decir, extrae los requisitos de tipo para los parámetros i ~ j de un tipo de función a un tipo de tupla, difiriendo la verificación de tipo de los argumentos a la aplicación de la función.

// helpers in https://gist.github.com/tycho01/be27a32573339ead953a07010ed3b824, too many to include

// poor man's version, using a given return value rather than using `typeof` based on the given argument types:
function curry<Args extends any[], Ret>(fn: (...args: Args) => Ret): Curried<Args, Ret>;
type Curried<
  ArgsAsked,
  Ret,
  ArgsPrevious = [] // if we can't have empty tuple I guess any[] might also destructures to nothing; that might do.
> = <
  ArgsGiven extends any[] = ArgsGiven,
  ArgsAll extends [...ArgsPrevious, ...ArgsGiven]
      = [...ArgsPrevious, ...ArgsGiven]
  >(...args: ArgsGiven) =>
    If<
      TupleHasIndex<ArgsAll, TupleLastIndex<ArgsAsked>>,
      Ret,
      Curried<ArgsAsked, Ret, ArgsAll>
    >;

// robust alternative that takes into account return values dependent on input params, also needs #6606
function curry<F>(fn: F): Curried<F>;
type Curried<
  F extends (...args: ArgsAsked) => any,
  ArgsAsked extends any[] = ArgsAsked,
  ArgsPrevious = []
> = <
  ArgsGiven extends any[] = ArgsGiven,
  ArgsAll extends [...ArgsPrevious, ...ArgsGiven]
      = [...ArgsPrevious, ...ArgsGiven]
  >(...args: ArgsGiven) =>
    If<
      TupleHasIndex<ArgsAll, TupleLastIndex<ArgsAsked>>,
      F(...[...ArgsPrevious, ...ArgsGiven]), // #6606
      Curried<ArgsAsked, Ret, ArgsAll>
    >;

// bind:
interface Function {
    bind<
        F extends (this: T, ...args: ArgsAsked) => R,
        ArgsAsked extends any[],
        R extends any,
        T,
        Args extends any[], // tie to ArgsAsked
        Left extends any[] = DifferenceTuples<ArgsAsked, Args>,
        EnsureArgsMatchAsked extends 0 = ((v: Args) => 0)(TupleFrom<ArgsAsked, TupleLength<Args>>)
        // ^ workaround to ensure we can tie `Args` to both the actual input params as well as to the desired params. it'd throw if the condition is not met.
    >(
        this: F,
        thisObject: T,
        ...args: Args
    ): (this: any, ...rest: Left) => R;
    // ^ `R` alt. to calc return type based on input (needs #6606): `F(this: T, ...[...Args, ...Left])`
}

Sí, utilicé un montón de tipos de ayudantes, solo tratando de conformarnos con lo que tenemos (+ imagina lo que podríamos hacer con un poco más). No estoy tanto en contra de ...... , ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> , o <PromiseLike<T*>> . Pero en mi opinión, incluso solo ... ayuda a abordar un problema real en este momento, y me encantaría que se solucione.

Editar: resolví la restricción del argumento para bind .

Probablemente sea una pregunta estúpida. Esto parece muy prometedor para poder escribir correctamente las funciones de curry.
Pero para algunos proyectos del mundo real, uno no podría querer dedicar mucho tiempo a escribir un fragmento de código orientado a la programación altamente funcional.
Entonces, me pregunto, dado que --strict está habilitado de forma predeterminada en _tsconfig.json_, si podría haber una forma de deshabilitar la verificación de tipos para una parte del código (debido a la pereza o la falta de tiempo).
Pero, como dije, probablemente sea una pregunta estúpida ... ^ _ ^

@ yahiko00 un poco fuera de tema, pero usa la sección exclude en tsconfig o diferente tsconfig s en diferentes niveles de proyecto.

También me gustaría hacer otra sugerencia, ¿podríamos tenerla para que & y | funcionen con un solo argumento de tupla con esta sintaxis:

<...T>(...args:T): ...T&
// is the same as 
<t1, t2, t3>(...args:[t1, t2, t3]): t1 & t2 & t3;
// and
<....T>(...args:T): ...T|
// is the same as 
<t1, t2, t3>(...args:[t1, t2, t3]): t1 | t2 | t3;

La sugerencia anterior de @HyphnKnight sería muy útil para algo que yo también estoy haciendo.

Quiero añadir un descargo de responsabilidad de que no se está trabajando activamente en esta propuesta. Pero encontré exactamente el tipo de documento de "estado de la técnica" que quería leer cuando comencé a analizar este problema: http://www.ccs.neu.edu/racket/pubs/esop09-sthf.pdf

Lo dejo aquí para referencia futura.

Abrí algunos PR experimentando con esto:

  • [] # 17884 se propaga en tipos de tupla (WIP)
  • [x] # 17898 extraer parámetros de descanso (listo)
  • [] # 18007 se propaga en tipo de llamada (WIP)
const c = 'a' + 'b';

¿Puede resolver el problema? Inferir tipo de c es 'ab' no string

Una pregunta relacionada en StackOverflow: último parámetro de función explícito en TypeScript

@sandersn Su propuesta cubriría este caso, por lo que puedo ver, ¿es correcto?

Más de 2 años desde la propuesta inicial, ¿deberíamos seguir teniendo esperanzas?

¡Hola!
Estoy tratando de escribir un generador que toma un número variable de matrices y mezcla y combina elementos de ellas para crear una nueva matriz.
Quiero usar este generador en un bucle for...of , pero no puedo escribir correctamente los valores.
Código (puede haber errores ya que aún no lo he ejecutado, pero esto es lo que estoy tratando de hacer):

function* CombineEveryArgumentWithEveryArgument(...args: any[][]) {
    if (args.length < 1) {
        return [];
    }
    var haselements = false;
    for (var arg of args) {
        if (arg && arg.length > 0) {
            haselements;
        }
    }
    if (!haselements) {
        return [];
    }
    var indexes = [];
    for (var i = 0; i < args.length; i++) {
        indexes.push(0);
    }
    while (true) {
        var values = [];
        //One item from every argument.
        for (var i = 0; i < args.length; i++) {
            values.push(args[i][indexes[i]]);
        }
        if (indexes[0] + 1 < args[0].length) {
            yield values;
        }
        else {
            return values;
        }
        //Increment starting from the last, until we get to the first.
        for (var i = args.length; i > 0; --i) {
            if (indexes[i]++ >= args[i].length) {
                indexes[i] = 0;
            }
            else {
                break;
            }
        }
    }
}

Uso de ejemplo:

for (let [target, child] of
    CombineEveryArgumentWithEveryArgument(targetsarray, childrenarray)) {

No puedo encontrar ninguna forma de escribir para el objetivo y el niño sin crear una variable intermedia.

¿Algo como esto sería bueno?

function * generator<...T[]>(...args: T[]): [...T]

@Griffork la práctica correcta, hasta que se implemente esta propuesta, es crear muchas sobrecargas para las funciones
por ejemplo, vea la promesa. todos los tipos
https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L41 -L113

Encuentro esta sintaxis muy confusa:

function apply<...T,U>(ap: (...args:...T) => U, args: ...T): U {

esto me parece mucho más natural:

function apply<T, U>(ap: (...args: T) => U, args: T): U {

En tiempo de ejecución, un parámetro de descanso es una matriz, y podemos hacer esto actualmente en TS:

function apply<T, U>(ap: (...args: T[]) => U, args: T[]): U {

Por lo tanto, parece lógico eliminar la restricción de que args sea ​​una matriz de T y, en su lugar, permitir que el compilador de TS infiera un tipo de tupla para T , por ejemplo

function apply(ap: (...args: [number, number]) => number, args: [number, number]): number {

Vi que surgieron algunas preocupaciones acerca de las tuplas y no las entiendo completamente, pero solo quería sopesar que en la propuesta actual es difícil entender cuándo un desarrollador necesitaría usar ... en una posición de tipo y las tuplas son mucho más intuitivas.

... embargo, [...T, ...U] .

@felixfbecker
La propuesta de

function apply<...T,U>(ap: (...args:T) => U, ...args: T): U {

Sería que T es un tipo de tupla creado dinámicamente, por lo que si pasa un string y un int en la función, entonces T es [string, int] .
Esto es particularmente interesante si desea expresar dinámicamente un patrón como este:

function PickArguments<T>(a: T[]): [T];
function PickArguments<T, U>(a: T[], b: U[]): [T, U];
function PickArguments<T, U, V>(a: T[], b: U[], c: V[]): [T, U, V];
//More overloads for increasing numbers of parameters.

//usage:
var [a, b, c] = PickArguments(["first", "second", "third"], [1, 2, 3], [new Date()]);
var d = b + 1; //b and d are numbers.
var e = c.toDateString(); //c is a date (autocompletes and everything), e is a string.

Por el momento, si desea escribir una función que tome un número variable de argumentos y los escriba de forma genérica, debe escribir una sobrecarga genérica para cada número de argumentos que posiblemente se le pueda dar a esa función. La propuesta ...T esencialmente nos permite hacer que el compilador genere automáticamente las definiciones de función para nosotros.

Tu propuesta:

function apply<T, U>(ap: (...args: T) => U, args: T): U {

Obliga a que todos los parámetros se traten como del mismo tipo y no pueden tener una verificación de tipo más específica. Por ejemplo, en mi ejemplo anterior, todos los valores devueltos serían del tipo any .

También encuentro que el adicional ... es muy difícil de leer.
Como la idea de @felixfbecker , no veo la necesidad de hacer:

function apply<...T, U>(ap: (...args: ...T) => U, args: ...T): U {...}

Lo primero que me viene a la mente al leer apply<...T, es que es un operador de propagación, pero en realidad no lo hace en absoluto.

@Griffork , en su ejemplo T aún sería [string, int] .
Eso es lo que @felixfbecker quiere decir con "en lugar de permitir que el compilador de TS infiera un tipo de tupla para T", al menos de la forma en que yo lo entiendo.

Obliga a que todos los parámetros se traten como del mismo tipo y no pueden tener una verificación de tipo más específica. Por ejemplo, en mi ejemplo anterior, todos los valores devueltos serían de tipo any.

@Griffork No, en mi opinión, args , dando a cada parámetro su propio tipo por su posición en la tupla. ...args: T[] los obligaría a todos a ser del mismo tipo T , pero ...args: T (que actualmente es un error de compilación) inferiría un tipo de tupla para T .

Lo primero que me viene a la mente al leer apply <... T, es que es un operador de propagación, pero en realidad no lo hace en absoluto.

@unional de acuerdo, de ahí es exactamente de donde proviene la confusión.

@unional
Lo leí como el operador de propagación también, lo leí como "propagación de este tipo cada vez que se usa".
Para mi, leyendo esto

function apply<T, U>(ap: (...args: T) => U, args: T): U {

Esperaría que T sea ​​una matriz de algo (por ejemplo, string[] ).

Y leyendo esto:

function apply<T, U>(ap: (...args: T[]) => U, args: T[]): U {

Espero que todos los argumentos sean asignables al tipo T (que es un tipo, como string ).

El punto hacia la propuesta anterior era evitar implícitamente hacer que los genéricos pudieran representar una cantidad arbitraria de tipos.

@felixfbecker
Editar:
Ah, OK. Todavía no creo que sea intuitivo.

Esperaría que T sea una matriz de algo (por ejemplo, cadena []).

Una tupla es "algo matriz", es solo una matriz con una longitud fija y tipos específicos para cada elemento, por ejemplo, [string, number] (frente a (string | number)[] , que no está vinculado y no declara qué elemento tiene qué escribe).

Bueno, entonces, ¿qué escribes si realmente quieres ese comportamiento?

No estoy seguro de a qué comportamiento se refiere exactamente, pero supongo que "forzará todos los parámetros a ser del mismo tipo", lo que se lograría con ...args: T[] .

Lo leí como el operador de propagación también, lo leí como "propagación de este tipo cada vez que se usa".

Por eso creo que es confuso.
Cuando se propaga, simplemente lo hace, no declarará algo que sea "propagable":

const a = { x: 1, y: 2 }
const b = { ...a }

// likewise
function appendString<T>(...args: T): [...T, string] {
  args.push('abc')
  return args
}

Sí. Si desea declarar que un argumento de tipo genérico debe ser "expandible" (lo que según la especificación ES solo significa que debe ser iterable), ya tenemos una forma de expresarlo en TypeScript con extends :

function foo<T extends Iterable<any>>(spreadable: T): [...T, string] {
  return [...spreadable, 'abc']
}

const bar = foo([1, true])
// bar is [number, boolean, string]

por supuesto, en el caso de un parámetro de descanso se sabe que no es solo Iterable, sino un Array.

Que, lo que estamos diciendo ya se ha propuesto: https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189703556

Pero el resto es demasiado largo para consumirlo en un solo asiento. 🌷

Si la concatenación de tuplas está aterrizando, ¡entonces podemos implementar números de iglesia! ¡Hurra!

type TupleSuc<T extends [...number]> = [...T, T['length']];
type TupleZero = [];  // as proposed, we need empty tuple
type TupleOne = TupleSuc<TupleZero>;
type Zero = TupleZero['length'];
type One = TupleOne['length'];

Y si la recursividad de tipo condicional está funcionando, podemos crear una tupla con la longitud deseada:

type Tuple<N extends number, T = TupleZero> = T['length'] extends N ? T : Tuple<N, TupleSuc<T>>;
type TupleTen = Tuple<10>;
type Ten = TupleTen['length'];

No supongo que haya leído todo este hilo, pero si tener ...T en parámetros genéricos es confuso,
¿Por qué no intentar reflejar más la sintaxis de desestructuración a nivel de valor, de modo que el uso de [...T] en un
¿La posición del argumento a nivel de tipo desestructura el tipo si es una tupla a nivel de tipo? Esto también requeriría
permitiendo escribir parámetros de descanso con tuplas, lo que haría el siguiente equivalente en el sitio de la llamada
en TypeScript:

const first = (a: number, b: string) => …;
const second = (...ab: [number, string]) => …;

first(12, "hello"); // ok
second(12, "hello"); // also ok

INB4 "pero eso es una emisión dirigida por tipo" - no. Esto no cambia la emisión en absoluto, el first todavía tiene dos
distintos argumentos emitidos, el second todavía tendrá un argumento de descanso. Lo único que cambia es
que en el sitio de la llamada, TypeScript verificará que los parámetros de second coincidan, en orden, con la tupla
[number, string] .

De todos modos, asumiendo que admitimos la sintaxis [...Type] , entonces podríamos escribir apply siguiente manera:

function apply<
  [...ArgumentsT], // a type-level tuple of arguments
  ResultT
>(
  // the call site of `toApply` function will be used to infer values of `ArgumentsT`
  toApply:   (...arguments: ArgumentsT) => ResultT,
  arguments: ArgumentsT
) :
  ResultT
{
  // …
}

// NB: using my preferred formatting for complex type-level stuff; hope it's readable for you
// this is entirely equivalent to OP's notation version:
function apply<[...T], U>(ap: (...args: T) => U,  args: T): U {
  // …
}

// so at the call site of
const fn = (a: number, b: string, c: RegExp) => …;

// we have `ArgumentsT` equal to [number, string, RegExp]
apply(fn, [12, "hello" /s+/]); // ok, matches `ArgumentsT`
apply(fn, [12, /s+/]); // not ok, doesn't match `ArgumentsT`

La sintaxis [...Type] se comportaría completamente como desestructuración a nivel de valor, lo que permite dividir
y uniendo tuplas de nivel de tipo, según sea necesario:

type SomeType  = [string, number, "constant"];
type OtherType = ["another-constant", number];

type First<[First, ..._]> = FirstT;
type Rest<[_, ...RestT]> = RestT;
type Concat<[...LeftT], [...RightT]> = [...LeftT, ...RightT];
type FirstTwo<[FirstT, SecondT, ..._]> = [FirstT, SecondT];

// has type `string`
const aString: First<SomeType> =
  "strrriiing";
// has type `[number, "constant"]
const numberAndConstant: Rest<SomeType> =
  [42, "constant"];
// has type `[string, number, "constant", "another-constant", number]`
const everything: Concat<SomeType, OtherType> =
  ["herpderp", 42, "constant", "another-constant", 1337];
// has type `[string, number]`
const firstTwo: FirstTwo<SomeType> =
  ["striiiing", 42];

Un ejemplo de cómo escribir la función curry usando esto:

type Curried<
  [...ArgumentsT]
  ResultT,
  ArgumentT      = First<ArgumentsT>,
  RestArgumentsT = Rest<ArgumentsT>
> =
  // just ye olde recursione, to build nested functions until we run out of arguments
  RestArgumentsT extends []
    ? (argument: ArgumentT) => ResultT
    : (argument: ArgumentT) => Curried<RestArgumentsT, ResultT>;

// NB. with more complex generic types I usually use generic defaults as a sort-of
// of type-level variable assignment; not at all required for this, just nicer to read IMO

function curry<
  [...ArgumentsT],
  ResultT
>(
  function: (...arguments: ArgumentsT) => ResultT
) :
  Curried<ArgumentsT, ResultT>
{
  // do the magic curry thing here
}

// or in the short indecipherable variable name style

function curry<[...T], U>(fn: (...args: T) => U): Curried<T, U>
{
  // …
}

// this should let you do this (using `fn` from before)
const justAddRegex = curry(fn)(123, "hello");

justAddRegex(/s+/); // ok, matches the arguments of `fn`
justAddRegex(123); // not ok, doesn't match the arguments of `fn`

Supongo que también sería útil poder decir que algún argumento de tipo es una tupla de nivel de tipo
De algún tipo. El problema entonces sería cómo, considerando que 2.7 (¿creo?), La asignabilidad de tuplas requiere
en cuenta la longitud de la tupla - para expresar el concepto de _cualquier tupla de nivel de tipo_. Pero tal vez algo como
[...] podría funcionar? No tengo una opinión firme, pero sería bueno que el concepto tuviera nombre.

// bikeshed me
type OnlyTuplesWelcome<ArgumentT extends [...]> = ArgumentT;

En ese caso, la sintaxis anterior de [...ArgsT] podría ser básicamente una abreviatura de ArgsT extends [...] ,
tener el uso de desestructuración a nivel de tipo implica una restricción en el tipo de ser una tupla a nivel de tipo.

¿Pensamientos?

@jaen :

(...ab: [number, string]) => …

Sí, parece el número 4130. Intenté algo en # 18004, pero mi enfoque fue un poco hacky (nodos sintéticos).

Al expresar cualquier tupla, había visto a alguien usando any[] & { 0: any } , que supongo que funciona hasta que la tupla vacía escriba fwiw. No me he preocupado mucho personalmente, en su mayoría solo me conformé con any[] .

RxJS necesita esto por todas partes. Lo más crítico para Observable.prototype.pipe , para el que actualmente tenemos muchas sobrecargas, pero siempre me piden que agregue "solo un nivel más".

En segundo lugar, @benlesh usamos RXJS extensamente y lo necesitamos para funciones de tubería.

Soy el autor de ppipe , que, al igual que las funciones de tubería en RXJS, necesita esto. Creo que veo un patrón aquí ^^

Soy el autor de runtypes , y esta función se necesita desesperadamente para expresar uniones e intersecciones. La única solución alternativa (incompleta) son las sobrecargas gigantescas:

https://github.com/pelotom/runtypes/blob/master/src/types/union.ts

🤢

Reescribí los tipos de pipe , lentes y curry.
Necesitábamos codegen ya que el curry hacía que las sobrecargas fueran directamente inmanejables. Nuestro tipo path abarcaba más de mil líneas, momento en el que descubrimos que el rendimiento del tipo de sobrecarga también se había vuelto problemático.

¿Se ha resuelto el problema de inferir y aplicar argumentos en reposo?

function example(head: string, ...tail: number[]): number[] {
  return [Number(head), ...tail]
}

function apply<T, U>(fn: (...args: T) => U, args: T): U {
  return fn.apply(null, args)
}

Si el tipo de T en apply(example, ['0', 1, 2, 3]) se infiere como [string, number[]] , la llamada a aplicar generaría un error.

Eso significa que el tipo de T es realmente

type T = [string, ...number[]]

o

type T =
  {0: string} &
  {[key: Exclude<number, 0>]: number} &
  Methods

Ciertamente una bestia extraña, pero dado que ({0: string} & Array<number>)[0]
actualmente se resuelve en string [1] parece posible codificar sin muchos cambios
al sistema de tipos.

[1] ¿Es esto un error? ¿Debería realmente ser string | number ?

Lamento molestar a 36 participantes de este problema (consejo: use esto ), pero ¿cómo podemos monitorear si esto todavía se está considerando, si está en la hoja de ruta, etc.?

Es triste que aún no se haya asignado a nadie después de 2 años y medio, parece ser una característica bastante importante :(

PD: Leí un par de docenas de comentarios, probé Cmd + F, etc., no encontré esta información.

@brunolemos hay una referencia a los tipos variadic en la última reunión de diseño
https://github.com/Microsoft/TypeScript/issues/23045
Para hacer esta función, primero deben crear conceptos más primitivos y iterativos, y cuando haya suficientes fundamentos, estoy seguro de que lo agregarán a cualquier hito.

no puedo hacer esto

type Last<T extends any[]> =
    T extends [infer P] ? P :
    ((...x: T) => any) extends ((x: any, ...xs: infer XS) => any) ? Last<XS> :

Es una cuestión de # 14174 pero como una relación

@kgtkr como referencia, vea el truco de @fightingcat para que el compilador pase por alto la recursividad.

Gracias

type Last<T extends any[]> = {
    0: never,
    1: Head<T>,
    2: Last<Tail<T>>,
}[T extends [] ? 0 : T extends [any] ? 1 : 2];

Hmm, tengo una pregunta. Tengo un código como este para manejar mixins:

export const Mixed = <

    OP = {}, OS = {}, // base props and state
    AP = {}, AS = {}, // mixin A props and state
    BP = {}, BS = {}, // mixin B props and state
    // ...and other autogenerated stuff
>(

    // TODO: Find a way to write that as ...args with generics:
    a?: ComponentClass<AP, AS>,
    b?: ComponentClass<BP, BS>,
    // ...and other autogenerated stuff

) => {

    type P = OP & AP & BP;
    type S = OS & AS & BS;
    const mixins = [a, b];

    return class extends Component<P, S> {
        constructor(props: P) {
            super(props);
            mixins.map(mix => {
                if (mix) {
                    mix.prototype.constructor.call(this);
                    // some state magic...
                }
            });
        }
    };
};

Lo uso como sigue:

class SomeComponent extends Mixed(MixinRedux, MixinRouter, MixinForm) {
     // do some stuff with mixed state
}

Funciona como se esperaba, con la escritura adecuada, el manejo de estados, etc., pero ¿hay alguna forma de reescribir de una manera más corta sin esperar un tipo variado? Porque me siento un poco tonto con esto en este momento.

Con 3.0 ahora es posible declarar rest args como tupple

declare function foo(...args: [number, string, boolean]): void;

Pero, ¿es posible obtener a la inversa para obtener el tipo de tupla de argumentos de la función dada?

Algo como Arguments<foo> estaría bien.

@whitecolor ¿qué tal esto?

type Arguments<F extends (...x: any[]) => any> =
  F extends (...x: infer A) => any ? A : never;

con TS 3.0 podemos hacer esto ahora

function compose<X extends any[], Y extends any[], Z extends any[]>(
  f: (...args: X) => Y,
  g: (...args: Y) => Z
): (...args: X) => Z {
  return function (...args) {
    const y = f(...args);
    return g(...y);
  };
}

pero tenemos un pequeño problema, tenemos que declarar funciones que devuelven eventos tupples para parámetros individuales y también lidiar con void de alguna manera y tenemos que declarar el tipo de retorno, de lo contrario, se infiere como una matriz :)

https://www.typescriptlang.org/play/index.html#src =% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0Función% 20foo0 () % 3A% 20 vacío% 20% 7B% 0D% 0A% 20% 0D% 0A% 7D% 0D% 0A% 0D% 0Función% 20bar0 ()% 3A% 20 vacío% 20% 7B% 0D% 0A% 0D% 0A% 7D % 0D% 0A% 0D% 0Afunción% 20foo1 (a% 3A% 20string)% 3A% 20% 5Bstring% 5D% 20% 7B% 0D% 0A% 20% 20return% 20% 5Ba% 5D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0Función% 20bar1 (a% 3A% 20string)% 3A% 20% 5Bstring% 5D% 20% 7B% 0D% 0A% 20% 20return% 20% 5Ba% 5D% 3B% 0D% 0A % 7D% 0D% 0A% 0D% 0Función% 20foo2 (a1% 3A% 20string% 2C% 20a2% 3A% 20boolean)% 3A% 20% 5Bstring% 2C% 20boolean% 5D% 20% 7B% 0D% 0A% 20% 20return% 20% 5Ba1% 2C% 20a2% 5D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0Afunction% 20foo21 (a1% 3A% 20string% 2C% 20a2% 3A% 20boolean)% 3A% 20% 5Bstring % 5D% 20% 7B% 0D% 0A% 20% 20retorno% 20% 5Ba1% 5D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0A% 0D% 0Afunción% 20bar2 (a1% 3A% 20cadena% 2C % 20a2% 3A% 20boolean)% 3A% 20% 5Bstring% 2C% 20boolean% 5D% 20% 7B% 0D% 0A% 20% 20return% 20% 5Ba1% 2C% 20a2% 5D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0A% 0D% 0Función% 20compose% 3CX% 20extends% 20any% 5B% 5D% 2C% 20Y% 20extends% 20any% 5B% 5D% 2C% 20Z% 20extends% 20any% 5B% 5D% 3E ( % 0D% 0A% 20% 20f% 3A% 20 (... argumentos% 3A% 20X)% 20% 3D% 3E% 20Y% 2C% 0D% 0A% 20% 20g% 3A% 20 (... argumentos% 3A% 20Y)% 20% 3D% 3E% 20Z% 0D% 0A )% 3A% 20 (... args% 3A% 20X)% 20% 3D% 3E% 20Z% 20% 7B% 0D% 0A% 20% 20return% 20function% 20 (... args)% 20% 7B% 0D% 0A% 20% 20% 20% 20const% 20y% 20% 3D% 20f (... args)% 3B% 0D% 0A% 20% 20% 20% 20retorno% 20g (... y)% 3B% 0D% 0A% 20% 20% 7D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0Aconst% 20baz0% 20% 3D% 20compose (% 0D% 0A % 20% 20foo0% 2C% 0D% 0A% 20% 20bar0% 0D% 0A)% 3B% 0D% 0A% 0D% 0Aconst% 20baz21% 20% 3D% 20compose (% 0D% 0A% 20% 20foo21% 2C% 0D % 0A% 20% 20bar1% 0D% 0A)% 3B% 0D% 0Aconst% 20baz2% 20% 3D% 20compose (% 0D% 0A% 20% 20foo2% 2C% 0D% 0A% 20% 20bar2% 0D% 0A)% 3B% 0D% 0A% 0D% 0A% 0D% 0Aalert (baz2 ('a'% 2C% 20false))% 0D% 0Aalert (baz21 ('a'% 2C% 20true))% 0D% 0Aalert (baz0 ())

@maciejw
Utilice tipos condicionales:

function compose<X extends any[], Y extends any, Z extends any>(
  f: (...args: X) => Y,
  g: Y extends any[] ? (...args: Y) => Z : () => Z
): (...args: X) => Z {
    return function (...args) {
        const y = (f as any)(...args);
        return (g as any)(...y);
    } as any;
}

claro, pero esto es un poco hacky con esos as any :) Creo que sería genial si type system soportara esto sin hacks

Bueno, los tipos en el cuerpo de la función pueden ser múltiples cosas, realmente no hay mucho que se pueda hacer al respecto; podrías hacer algo como (f as (...args: any[]) => Y) lugar, pero creo que eso reduce la claridad sin una razón real

Si los tipos variadic se concretan, podría generalizar algún código TypeScript que escribí para mi propio proyecto, que me permitiera definir completamente la forma de mi API REST y aplicar esa forma en los tipos del servidor de nodo correspondiente y JavaScript. biblioteca cliente para ello.

Generalizar eso me permitiría simplificar mi propio código, así como hacer lo mismo para API arbitrarias, ir más allá y probablemente generar definiciones de Swagger para otros clientes de lenguaje también ... ¡podría ser útil para otros! Solo soñando en voz alta aunque jaja

@kgtkr : ¡Se ve genial! :)

El tipo Pipe bloquea el patio de juegos de TS para mí (aunque otros funcionan bien), ¿supongo que necesita el TS más nuevo?

TS también muestra algunos errores de profundidad de recursividad; parece que @isiahmeadows abrió # 26980 para eso.

@ tycho01

El tipo Pipe bloquea el patio de juegos de TS para mí (aunque otros funcionan bien), ¿supongo que necesita el TS más nuevo?

También me cuelga, y tuve que piratear las herramientas de desarrollo para forzarlo a lanzar un error y salir de él.

TS también muestra algunos errores de profundidad de recursividad; parece que @isiahmeadows abrió # 26980 para eso.

Eso es por algo relacionado, pero diferente: levantar una restricción con tipos condicionales por dos razones:

  • Para facilitar la realización de trabajos más complejos, como la iteración de listas. También establecería el marco para abrir cosas como matemáticas de enteros a nivel de tipo sin bloquear el compilador o terminar en un lío ad-hoc.
  • Para abordar de manera más adecuada el problema de lo que hace que el sistema de tipos de TS sea Turing-completo, de modo que pueda potencialmente reificarse con herramientas que lo aprovechen o eliminar mediante la aplicación de una terminación comprobable en el futuro.

Si eliminar los tipos indexados no es suficiente para que el sistema de tipos no sea Turing completo tal como está, no dude en dejar un comentario allí para que pueda actualizarlo en consecuencia. (No propongo eliminarlo, por supuesto. Solo propongo manejarlo mejor internamente para advertir a las personas de posibles bucles infinitos).

Pensándolo bien, este tipo de tubería se siente como una forma realmente complicada de hacer (vs...: Params<T[0]>) => ReturnType<Last<T>> . Cualquier iteración más allá de eso (aparte de las comprobaciones de parámetros intermedios) probablemente se vuelva más útil con los tipos de retorno dependientes de la entrada.

@ tycho01 Está intentando escribir cosas como esta , donde el tipo es básicamente esto:

   f1,     f2,   ...,   fm,     fn    -> composed
(a -> b, b -> c, ..., x -> y, y -> z) -> (a -> z)

Usted tiene que iterar los parámetros de forma individual para escribir correctamente, ya que los parámetros siguientes parámetros dependen de los parámetros anteriores valores de retorno, y también hay que cuenta para el primer parámetro y el valor de retorno final (que @kgtkr 's no) . Mi versión "mejorada" en # 26980 hizo una optimización para usar un acumulador en su lugar, así que estaba iterando los argumentos mucho menos, pero también lo hizo más correcto.


Si miras el mío en el n. ° 26980, es un poco más claro lo que se supone que debe hacer (menos persecución de números), y ese es parte del motivo por el que presenté esa solicitud de función.

@ tycho01 @kgtkr Por cierto, actualicé ese error con un fragmento PipeFunc corregido , copiado aquí para mayor comodidad:

type Last<L extends any[], D = never> = {
    0: D,
    1: L extends [infer H] ? H : never,
    2: ((...l: L) => any) extends ((h: any, ...t: infer T) => any) ? Last<T> : D,
}[L extends [] ? 0 : L extends [any] ? 1 : 2];

type Append<T extends any[], H> =
    ((h: H, ...t: T) => any) extends ((...l: infer L) => any) ? L : never;

type Reverse<L extends any[], R extends any[] = []> = {
    0: R,
    1: ((...l: L) => any) extends ((h: infer H, ...t: infer T) => any) ?
        Reverse<T, Append<R, H>> :
        never,
}[L extends [any, ...any[]] ? 1 : 0];

type Compose<L extends any[], V, R extends any[] = []> = {
    0: R,
    1: ((...l: L) => any) extends ((a: infer H, ...t: infer T) => any) ?
        Compose<T, H, Append<R, (x: V) => H>>
        : never,
}[L extends [any, ...any[]] ? 1 : 0];

export type PipeFunc<T extends any[], V> =
    (...f: Reverse<Compose<T, V>>) => ((x: V) => Last<T, V>);

Este no se estrella en el patio de recreo, por cierto. En realidad, realiza comprobaciones de tipo y lo hace con bastante rapidez.

Todavía no lo he probado en un tipo _.flow o _.flowRight potencial, pero eso debería funcionar como punto de partida.

@ tycho01
requerido
mecanografiado @ siguiente
3.0.1 / 3.0.2 no funciona

Gracias a este hilo, hice esto

Personas, por favor dejen de publicar información apenas relevante para la discusión de este tema. Hay mucha gente siguiendo esta discusión, porque queremos tipos variados. He recibido más de 10 correos electrónicos en los últimos días que son irrelevantes para las razones por las que sigo este problema.
Espero que haya otros de acuerdo conmigo. Hasta este momento, solo esperaba que se detuviera, porque no quería contribuir al spam. Pero en serio, ya es suficiente.
PD: Perdón por esta notificación, para cualquiera que esté tan harto de ellos como yo.

@Yuudaari Señalaré que escribir _.flow Lodash, _.compose Ramda, etc. es una de las cosas que impulsan este error, y una escritura exitosa es parte de la solución de este problema. De hecho, esa es una de las razones enumeradas en la descripción del problema original.

Realmente, en este punto, soy de la opinión de que el 99% de los problemas que existen hoy en día con las variantes son de ergonomía, no de funcionalidad. Podemos escribir Function.prototype.bind y Promise.all a la perfección con una mezcla de tipos de indexado, tipos de condicionales y recursividad (se puede hacer una repetida Append iteración a la lista de Function.prototype.bind , y Promise.all sería una iteración simple + Append ), solo que es muy incómodo y repetitivo hacerlo.

No estoy tratando de aumentar el ruido aquí, solo explicando que las cosas que comienzan aquí están técnicamente relacionadas con el tema, ya que se refieren a algunas de las razones por las que existe el error, incluso si no son las que le preocupan personalmente.

Creo que las personas que esperaban anuncios aquí se perdieron la gran noticia: resultó que las ahora posibles funciones Concat<T, U> exactamente como [...T, ...U] .

El subproceso Pipe trata de demostrar la funcionalidad que solicitamos aquí. Se trata de llegar al punto de este hilo, hoy.

Creo que eso significa que no estaríamos peor si cerramos este hilo, así que quizás este sea un buen momento para preguntar: ¿qué es lo que la gente todavía quiere de esta propuesta?

[es] solo que es muy incómodo y repetitivo

La mayoría de los tipos que usan esto usarán la recursividad ellos mismos, por lo que quienes los escriban definitivamente estarán familiarizados con ellos, mientras que los usuarios finales probablemente solo usarán bibliotecas con tipos predefinidos y escribirán su front-end, sin tener que saber que existe la iteración TS.

En ese momento, ¿quizás esta propuesta pueda mejorar principalmente el desempeño?

En primer lugar, ¿se pretende utilizar objetos de mapa para engañar al sistema de tipos para que haga recursividad? Me parece bastante extraño. Si uso una funcionalidad como esa (lo soy, pero eso es irrelevante), ¿no estará sujeta a fallas más adelante?

En segundo lugar, usar estas soluciones alternativas simplemente no es ... amigable. No es muy legible (especialmente para aquellos que no lo escribieron) y, como resultado, parece miserable de mantener.

¿Por qué querría recurrir a una propuesta que agrega las mismas características, de una manera intencionada, legible y mantenible, solo porque hay una solución para ellas?

No creo que la existencia de esta solución haga que esta propuesta se considere azúcar sintáctica, pero incluso si lo fuera, ¿por qué no querría azúcar sintáctica para este lío?

@Yuudaari

Editar: agregue un enlace para el contexto.

En primer lugar, ¿se pretende utilizar objetos de mapa para engañar al sistema de tipos para que haga recursividad? Me parece bastante extraño. Si uso una funcionalidad como esa (lo soy, pero eso es irrelevante), ¿no estará sujeta a fallas más adelante?

Eche un vistazo al error que presenté recientemente: # 26980. No es el único que cuestiona el patrón. Está un poco fuera de tema aquí, pero siéntase libre de intervenir allí.

Tenga en cuenta que hay un poco de matemática involucrada en cómo averiguar si algo recursivo termina (una de las principales razones por las que tiene tantos matices en primer lugar).

En segundo lugar, usar estas soluciones alternativas simplemente no es ... amigable. No es muy legible (especialmente para aquellos que no lo escribieron) y, como resultado, parece miserable de mantener.

¿Por qué querría recurrir a una propuesta que agrega las mismas características, de una manera intencionada, legible y mantenible, solo porque hay una solución para ellas?

No creo que la existencia de esta solución haga que esta propuesta se considere azúcar sintáctica, pero incluso si lo fuera, ¿por qué no querría azúcar sintáctica para este lío?

Existe una forma simplificada de iterar tuplas en el caso común de lo que es efectivamente Array.prototype.map , pero eso era básicamente inútil para mis necesidades (necesitaba un acumulador).

Personalmente, me gustaría azúcar sintáctico para estos:

  1. Concatenando dos listas a través de [...First, ...Second] .
  2. Agregar valores a través de [...Values, Item] .
  3. Extrayendo el último elemento a través de T extends [...any[], infer Last] .
  4. Extrayendo la cola a través de T extends [A, B, ...infer Tail] .

Combine eso con # 26980, y podría convertir los tipos anteriores en esto:

type Compose<L extends any[], V, R extends any[] = []> =
    L extends [infer H, ...infer T] ?
        Compose<T, H, [...R, (x: V) => H]> :
        R;

export type PipeFunc<T extends any[], V> =
    T extends [...any[], infer R] ?
        (...f: Compose<T, V>) => ((x: V) => R) :
        () => (x: V) => V;

Pero eso es todo. No veo mucho uso para ningún otro azúcar sintáctico, ya que casi todo aquí solo trata con tuplas, y los objetos ya tienen en gran parte todo lo que necesitarías para operaciones similares.

En primer lugar, ¿se pretende utilizar objetos de mapa para engañar al sistema de tipos para que haga recursividad? Me parece bastante extraño. Si uso una funcionalidad como esa (lo soy, pero eso es irrelevante), ¿no estará sujeta a fallas más adelante?

Creo que la palabra oficial es algo así como "no lo hagas". @ahejlsberg dijo :

Es inteligente, pero definitivamente empuja las cosas mucho más allá de su uso previsto. Si bien puede funcionar para pequeños ejemplos, escalará horriblemente. Resolver esos tipos profundamente recursivos consume una gran cantidad de tiempo y recursos y, en el futuro, podría entrar en conflicto con los gobernadores de recursividad que tenemos en el corrector.

¡No lo hagas!

☹️

@jcalz Entonces, ¿una razón más para que exista # 26980?

Cuando comencé a usar TS apenas el descanso de este año, ¡mi inclinación era escribir _sólo eso_! ( ...T ) con la esperanza de que sea la sintaxis de las tuplas de tipo variable variable. Bueno, espero que esto entre :)

Acabo de encontrar un nuevo uso para [...T, ...U] : escribir correctamente los constructores de HTML. Para un ejemplo concreto, los hijos de <video> deben ser los siguientes:

  • Si el elemento tiene un atributo src :

    • Cero o más <track> elementos

  • Si el elemento no tiene un atributo src :

    • Cero o más <source> elementos

  • Cero o más elementos según el modelo de contenido principal, excepto que no se permite ningún elemento descendiente audio o video .

Básicamente, esto equivale a este tipo, pero hoy en día no hay forma de expresarlo en TypeScript:

type VideoChildren<ParentModel extends string[]> = [
    ...Array<"track">, // Not possible
    ...{[I in keyof ParentModel]: P[I] extends "audio" | "video" ? never : P[I]},
]

3,5 años: /

Caso de uso:

type DrawOp<...T> = (G: CanvasRenderingContext2D, frame: Bounds, ...args: any[]) => void;
const drawOps: DrawOp<...any>[] = [];

function addDrawOp<...T>(fn: DrawOp<...T>, ...args: T) {
    drawOps.push(fn);
}

Solo veo las sobrecargas mencionadas brevemente en la sección de preguntas abiertas de la propuesta, pero definitivamente algo con lo que me he encontrado y sería excelente para ver una solución o una propuesta, por ejemplo:

  function $findOne(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    cb: Cb<TSchema>,
  ): void;
  function $findOne<T extends keyof TSchema>(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    projection: Projection<T>,
    cb: Cb<Pick<TSchema, T>>,
  ): void;
  function $findOne(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    projection: undefined,
    cb: Cb<TSchema>,
  ): void;
  function $findOne<T extends keyof TSchema>(
    ctx: ICtx,
    filter: mongodb.FilterQuery<TSchema>,
    projection: Projection<T> | Cb<TSchema> | undefined,
    cb?: Cb<Pick<TSchema, T>>,
  ): void {

  promisify($findOne) // this can't infer types correctly

actualmente esto no funciona en absoluto y simplemente escribe promisify como (ctx: ICtx, filter: FilterQuery<TSchema>) => Promise<TSchema[]> que está perdiendo información de estas firmas.

AFAICT, la única solución real para esto es básicamente crear una función de promesa y especificar manualmente todos los tipos posibles para esa variante; la única forma de proporcionar una firma respetada de la variante envuelta es no especificar sobrecargas y solo especificar la firma de implementación, pero no hay forma de que la persona que llama sepa cuál de los tipos de devolución debe esperar en función de los argumentos que pasa si especifica la firma de esa manera.

esto se ve agravado por el hecho de que los parámetros de descanso solo pueden ser el último parámetro (es decir, (cb, ...args) es válido pero no (...args, cb) , por lo que incluso si la firma es internamente un tipo de unión, en realidad no puede distribuir las cosas correctamente; por ejemplo, sería bastante sencillo si cb fuera siempre el primer argumento para escribir promisify como function promisify<T, V extends any[]>(fn: (cb: (err: Error | null, res?: T) => void, ...args: V)): (...args: V) => T y al menos podría obtener tipos de unión para firmas con la misma respuesta de retorno, pero porque es el parámetro final afacto, no hay mucho que se pueda hacer aquí

@ Qix- Su escenario está habilitado por # 24897. Se implementó en TS 3.0.

@ahejlsberg ¡Oof! Impresionante, gracias ♥ ️

Ha pasado mucho tiempo esperando ... Pero hoy es posible escribir tipos variados. TS tiene la madurez suficiente para escribir tipos complejos que funcionen. Así que me tomé el tiempo para escribir tipos para curry, concat , compose y pipe para Ramda.

Y ahora se envían con ts-toolbelt .

Sin embargo, esta propuesta es un buen azúcar sintáctico para facilitar mucho las manipulaciones de tuplas comunes.

¿Ya lo tienes en medium.com? URL?

Está el artículo original en Medium, pero la bonificación no está incluida, está en el repositorio. También explica cómo creé todas las pequeñas herramientas para componer, pipe & curry: smile:

@ pirix-gh, pero esto no es genérico variado como en esta propuesta

declare function m<...T>(): T

m<number, string>() // [number, string]

@goodmind Sí, no lo es, está más emulado. Entonces podrías emular el ... así:

declare function m<T extends any[], U extends any[]>(): Concat<T, U>

m<[number, string], [object, any]>() // [number, string, object, any]

Es lo mismo que:

declare function m<...T, ...U>(): [...T, ...U]

m<number, string, object, any>() // [number, string, object, any]

Mientras tanto, mientras espera esta propuesta: hourglass_flowing_sand:

@ pirix-gh, ¿puede ayudarme con la función de envoltura como

type fn = <T>(arg: () => T) => T
let test1: fn
let res1 = test1(() => true) // boolean

type fnWrap = (...arg: Parameters<fn>) => ReturnType<fn>
let test2: fnWrap
let res2 = test2(() => true) // {}

Estaba tratando de usar el enfoque de redacción pero fallé, ¿pueden sugerirme de una manera adecuada, por favor?

Esto sucede porque cuando extrae los parámetros / retorno de fn que dependen de los genéricos, TS los infiere a su tipo más cercano (en este caso T será any ). Así que no hay forma de hacerlo en este momento. Nuestra mejor esperanza es esperar a que esta propuesta se combine con https://github.com/Microsoft/TypeScript/pull/30215. Por lo tanto, tendrá que escribir sobrecargas de tipos.

O tal vez podríamos encontrar una manera de preservar / mover genéricos de tal manera que pudiéramos hacer:

declare function ideal<...T>(a: T[0], b: T[1], c: T[2]): T

ideal('a', 1, {}) // T = ['a', 1, {}]

De esta manera, reconstruiríamos fn partir de piezas. La parte que falta hoy es la parte genérica como señaló @goodmind .

@ pirix-gh Si no me equivoco, puedes hacer esto para lograr lo que tienes ahí arriba:

declare function MyFunction<A, B, C, Args extends [A, B, C]>(...[a, b, c]: Args): Args

const a = MyFunction(1, 'hello', true);
// typeof a = [number, string, boolean]

@ClickerMonkey No exactamente, porque lo que propuse funciona para una cantidad ilimitada de argumentos. Pero quizás también podríamos hacer esto, con lo que tú propusiste (no lo he visto en la propuesta):

declare function MyFunction<A, B, C, ...Args>(...[a, b, c]: Args): Args

const a = MyFunction(1, 'hello', true);
// typeof a = [number, string, boolean]

@ pirix-gh Los argumentos de tipo A , B y C en su ejemplo no se utilizan.

-declare function MyFunction<A, B, C, ...Args>(...[a, b, c]: Args): Args
+declare function MyFunction<...Args>(...[a, b, c]: Args): Args

Incluso si se implementaran tipos variadic, los dos ejemplos anteriores probablemente generarían un error de compilación, ¿cuál es el punto de los tipos variadic si solo desea tres argumentos?

Sus ejemplos deben mostrar por qué se necesita variadic, si se pueden hacer con el código TS existente, entonces no ayuda en absoluto a la causa.

@goodmind Sí, no lo es, está más emulado. Entonces podrías emular el ... así:

declare function m<T extends any[], U extends any[]>(): Concat<T, U>

m<[number, string], [object, any]>() // [number, string, object, any]

Es lo mismo que:

declare function m<...T, ...U>(): [...T, ...U]

m<number, string, object, any>() // [number, string, object, any]

Mientras tanto, a la espera de esta propuesta ⏳

¿De dónde sacaste los Concat<> ?

Editar: No importa, encontré el código fuente.

@ pirix-gh, así que intenté hacer esto con tus sugerencias, pero no pude resolverlo.

~ El problema es que estoy tratando de extender los parámetros del ctor de una clase y funciona hasta el punto de que tengo una matriz de tipos pero no puedo distribuirlos para los params de ctor. ~

Class Test {
  constructor(x: number, y: string) {}
}
let ExtendedClass = extendCtor<[number, string], [number]>(Test);

let instance = new ExtendedClass(1, '22', 2);

Actualización: no importa que también funcionó al usar una extensión en la función ctor.

Aquí está el enlace de la solución.

El único problema es que TS se bloquea casi siempre: |
y esto es lo que dice TypeScript Type instantiation is excessively deep and possibly infinite.ts(2589)

Actualización 2:
Lo logré poniendo el nuevo tipo al principio, aún así sería bueno poder fusionar estos tipos.

// ...
type CtorArgs<T, X> = T extends (new (...args: infer U) => any) ? [...U, X] : never;
// To be used as CtorArgs<typeof Test, string>
// ...
let instance = new MyClass1('22', 2, 'check');

Opuesto a:

let MyClass1 = extendClass<typeof Test, string>(Test);

let instance = new MyClass1('check', '22', 2);

Enlace a la solución final.

Si entiendo correctamente, Object.assign se puede declarar algo como lo siguiente para apoyar completamente los argumentos variadic.

type Assign<T, U extends any[]> = {
  0: T;
  1: ((...t: U) => any) extends ((head: infer Head, ...tail: infer Tail) => any)
    ? Assign<Omit<T, keyof Head> & Head, Tail>
    : never;
}[U['length'] extends 0 ? 0 : 1]

interface ObjectConstructor {
  assign<T, U extends any[]>(target: T, ...source: U): Assign<T, U>
}

¿Hay alguna razón por la que se declare de una manera diferente en lib.d.ts TypeScript?

No está usando tipos condicionales recursivos porque no son compatibles (vea # 26980 para una discusión de eso, o este comentario que nos dice que no hagamos eso). Si uno está dispuesto a usar los tipos de retorno de intersección actuales, hay # 28323.

@jcalz Creé una prueba pesada que muestra el tipo Minus en acción. Realiza los Minus 216000 veces en menos de 4 segundos. Esto muestra que TS puede manejar muy bien tipos recursivos. Pero esto es bastante reciente.

¿Por qué? Esto es gracias a Anders: tada: (https://github.com/microsoft/TypeScript/pull/30769). Me permitió cambiar de tipos condicionales a condiciones indexadas (como un interruptor). Y de hecho, mejoró el rendimiento en x6 para ts-toolbelt. Muchísimas gracias a él.

Entonces, técnicamente, podríamos reescribir el tipo de @kimamula de forma segura con ts-toolbelt. La complejidad sigue a O (n):

import {O, I, T} from 'ts-toolbelt'

// It works with the same principles `Minus` uses
type Assign<O extends object, Os extends object[], I extends I.Iteration = I.IterationOf<'0'>> = {
    0: Assign<O.Merge<Os[I.Pos<I>], O>, Os, I.Next<I>>
    1: O
}[
    I.Pos<I> extends T.Length<Os>  
    ? 1
    : 0
]

type test0 = Assign<{i: number}, [
    {a: '1', b: '0'},
    {a: '2'},
    {a: '3', c: '4'},
]>

La biblioteca también hace que la recursividad sea segura con Iteration que evitará cualquier desbordamiento de TypeScript. En otras palabras, si I supera 40 entonces se desborda y Pos<I> es igual a number . Deteniendo así la recursividad de forma segura.

Un tipo recursivo similar que escribí ( Curry ) se envía con Ramda , y parece que le va bien.

Por cierto, les agradezco (@jcalz) en la página del proyecto todos sus buenos consejos.

No estoy seguro de si el # 5453 es el mejor lugar para tener esta discusión ... ¿deberíamos hablar de esto en el # 26980 o hay una ubicación más canónica? En cualquier caso, me encantaría tener una forma oficial y compatible de hacer esto que posiblemente no implosione en las versiones posteriores de TypeScript. Algo que está incluido en sus pruebas de línea de base para que si se rompe lo arreglen. Incluso si se prueba que el rendimiento es bueno, desconfiaría de hacer esto en cualquier entorno de producción sin una palabra oficial de alguien como @ahejlsberg.

Algo que está incluido en sus pruebas de línea de base para que si se rompe lo arreglen.

Creo que tenemos algo bastante cercano que se usa internamente

@weswigham, perdóname por ser denso, pero ¿puedes mostrarme cómo el tipo resaltado es recursivo? Lo que me preocupa es la forma

type Foo<T> = { a: Foo<Bar<T>>, b: Baz }[Qux<T> extends Quux ? "a" : "b" ]

o cualquiera de las variantes que he visto. Si me falta algo y esto ha recibido algún tipo de luz verde, por favor, alguien me lo haga saber (¡y enséñeme cómo usarlo!)

Oh, justo, es diferente en ese sentido, sí. Solo digo el patrón "objeto indexado inmediatamente para seleccionar tipos" y me di cuenta de que teníamos _eso_.

Tengo una pregunta.

¿Cuánto de lo propuesto aquí sigue siendo relevante? Este número se abrió hace 4 años y siento que muchas cosas han cambiado desde entonces.

De mi comentario aquí,
https://github.com/microsoft/TypeScript/issues/33778#issuecomment -537877613

Yo dije,

TL; DR, tipos de tupla, argumentos en reposo, tipos de matriz mapeados, inferencia de tuplas para arg sin reposo, alias de tipo recursivo = sin necesidad real de compatibilidad con argumentos de tipo variable

Pero tengo curiosidad por ver si alguien tiene un caso de uso que simplemente no se puede habilitar con las herramientas existentes.

Hasta que obtengamos una versión oficialmente bendecida de Concat<T extends any[], U extends any[]> , esto sigue siendo relevante. No creo que la próxima función de referencia de tipo recursivo nos dé esto, pero me alegraría que me digan (con autoridad) lo contrario.

¿No tenemos ya implementaciones Concat<> ?

¿O la frase clave aquí es "oficialmente bendecida"?

Porque mi afirmación es que básicamente puedes hacer todo (¿o casi todo?) Que quieras en este momento, incluso si no está del todo "bendecido oficialmente".

Pero supongo que siempre debería preferirse "bendecido oficialmente" ... Buen punto. Estoy demasiado acostumbrado a (ab) usar esos alias de tipo recursivo

En general, preferiría una sintaxis real y elegante para que cada vez que haga algo como esto no tenga que seguir explicando a mis compañeros de equipo (a menudo junior) lo que sucede con los tipos confusamente especificados que requiere el abuso del status quo. Esa confusión daña mi capacidad para evangelizar TypeScript, o al menos estos usos de él, en mi organización.

¡Grande en esto!

Esta característica es MUY importante.

@AnyhowStep

Porque mi afirmación es que básicamente puedes hacer todo (¿o casi todo?) Que quieras en este momento.

No veo ninguna evidencia de que escribir un parámetro de propagación con varios parámetros individuales junto a él sea fácilmente alcanzable hoy en día en TS.

@ matthew-dean no es del todo cierto. Aquí hay un ejemplo que puede lograr hasta cierto punto.

Según tengo entendido, TS busca escribir la mayor cantidad posible de programas vanilla JS. Aquí hay un rompecabezas:

const f = <T extends any[]>(...args: T): T => args;
const g = <T extends any[]>(...a: T): WhatExactly<T> => {
    return f(3, ...a, 4, ...a, 5);
}
g(1, 2);

Esperaría que el tipo no sea más complejo que [number, ...T, number, ...T, number] . Si tenemos que escribir 20 líneas de algún código extraño que abusa de un error, verifique que tenga un tipo de retorno adecuado en la última línea, este problema no se resuelve.

@ polkovnikov-ph El tipo se infiere actualmente como: [number, ...any[]] , lo cual no es útil.

También me gustaría notar que no tenemos que respetar la décima regla de Greenspun durante 15 años como lo hizo C ++, porque C ++ ya pasó por todos los Head<> sy Cons<> por nosotros, y ideó una sintaxis de plantilla variada muy útil y limpia. Podemos ahorrar (cientos de años) el tiempo de los desarrolladores y simplemente tomar las mejores partes de allí.

Por ejemplo, los tipos variadic tienen un tipo diferente en C ++, por lo que no puede usar variables de tipo variadic donde se espera el tipo, a diferencia de un tipo que extends any[] en TS. Esto permite a C ++ mapear / comprimir sobre tuplas al mencionar la variable de tipo variadic dentro de alguna expresión encerrada en el operador de puntos suspensivos. Esta es más o menos una alternativa de tupla de tipos de objetos mapeados.

type Somethify<...T> = [...Smth<T>]
type Test1 = Somethify<[1, 2]> // [Smth<1>, Smth<2>]

type Zip<...T, ...U> = [...[T, U]]
type Test2 = Zip<[1, 2], [3, 4]> // [[1, 3], [2, 4]]

type Flatten<...T extends any[]> = [......T]
type Test3 = Flatten<[[1, 2], [3, 4]]> // [1, 2, 3, 4]

Mencione que la sintaxis de puntos suspensivos propuesta en lugar de extends any[] se usa en el ejemplo no solo por razones estéticas, sino porque

type A<T> = any[]
type B<T extends any[]> = [...A<T>]
type C = B<[1, 2]>

ya es un programa TS válido. C termina siendo any[] lugar de [any[], any[]] que generaría el tipo variable mapeado.

@DanielRosenwasser Me disculpo por

Por otro lado, el simple hecho de tener operaciones de propagación a nivel de tipo para los tipos de tupla sería de gran ayuda para mi equipo, incluso si la falta de tipos variados significa que no se pueden usar con parámetros de tipo. En nuestro dominio de problemas, las "matrices con alguna estructura" son muy comunes. Nos simplificaría mucho las cosas si esta operación funcionara:

type SharedValues = [S1, S2, S3];
type TupleOfSpecificKind = [V1, ...SharedValues, V2];

@sethfowler, si tiene algunos ejemplos de lo que quiere expresar, eso siempre es útil para nosotros. De lo contrario, podría estar interesado en https://github.com/microsoft/TypeScript/issues/26113

@DanielRosenwasser Claro, puedo hacer las cosas un poco más concretas. Mantendré el meollo de la cuestión, pero en un nivel alto, puede pensar que nuestro proyecto genera un flujo de operaciones gráficas y otros eventos similares que se envían a un servidor remoto. Por razones de eficiencia, necesitamos representar esas operaciones en la memoria en un formato que se pueda traducir directamente a su forma serializada. Los tipos de estos eventos terminan pareciéndose a esto:

type OpLineSegment = [
  StrokeColor,
  FillColor,
  number,  // thickness
  number, number, number,  // X0, Y0, Z0
  number, number, number  // X1, Y1, Z1
];
type OpCircle = [
  StrokeColor,
  FillColor,
  number, number, number,  // X, Y, Z of center
  number // radius
];
type OpPolygon = (StrokeColor | FillColor | number)[];  // [StrokeColor, FillColor, repeated X, Y, Z]]
type OpFan = (StrokeColor | FillColor | number)[];  // StrokeColor, FillColor, repeated X, Y, Z up to 10x

Nos gustaría poder expresar estos tipos más así:

type Colors = [StrokeColor, FillColor];
type Vertex3D = [number, number, number];

type OpLineSegment = [...Colors, number /* thickness */, ...Vertex3D, ...Vertex3D];
type OpCircle = [...Colors, ...Vertex3D, number /* radius */];
type OpPolygon = [...Colors, ...Repeated<...Vertex3D>];
type OpFan = [...Colors, ...RepeatedUpToTimes<10, ...Vertex3D>];

Tenemos una gran cantidad de estos comandos, por lo que solo tener una extensión a nivel de tipo daría como resultado un código mucho más fácil de mantener. Tener tipos variables para que podamos escribir funciones de nivel de tipo como Repeated<> y RepeatedUpToTimes<> (que evaluarían las uniones de tipos de tuplas definidas de forma recursiva en este ejemplo) iría aún más lejos para simplificar las cosas.

También sería inmensamente útil tener soporte para cosas como la concatenación segura de tipos de tipos de tuplas (como se explica en el OP). Para utilizar los tipos anteriores, actualmente tenemos que construir la tupla completa en una sola expresión literal de tupla. No podemos construirlo en partes y concatenarlas juntas en este momento. En otras palabras, las operaciones a continuación no funcionan hoy, pero realmente desearíamos que lo hicieran.

const colors: Colors = getColors();
const center: Vertex3D = getCenter();

// Doesn't work! Produces a homogenous array.
const circle1: OpCircle = [...colors, ...center, radius];

// Doesn't work; can't write this function today.
const circle2: OpCircle = concat(colors, center, radius);

// We need to do this today; it's quite painful with more complex tuple types.
const circle3: OpCircle = [colors[0], colors[1], center[0], center[1], center[2], radius];

¡Esperamos que estos ejemplos sean útiles!

Puede escribir fácilmente un tipo Concat<> y hacer un tipo Concat3<> , usando Concat<> .

Luego,

type OpCircle = Concat3<Colors, Vertex3D, [number] /* radius */>;

De lo anterior, puede escribir una función concat con sobrecargas para 2,3,4,5,6, etc. número de argumentos.

Incluso es posible escribir un Concat <> impl que toma una tupla de tuplas y concatena las tuplas. Un tipo var-arg Concat <>.


No es algo que no se pueda hacer hoy. Se puede hacer, incluso si requiere que escriba un tipo recursivo y una función auxiliar.

Cada vez que utilizo esos tipos recursivos en vscode, TS quiere matar la CPU o simplemente se cuelga. Ese es el problema principal, siento que TS se está volviendo demasiado pesado sin una buena razón.

¿Quizás las personas que escriben los tipos no están haciendo lo suficiente para optimizarlo?

No quiero enviar spam o arrastrar las viejas conversaciones que no agregarán mucho valor a este hilo, pero recuerdo que en algún momento escuché que el cálculo de tipos condicionales recursivos es caro en javascript (lo encontré aquí en este hilo )

Dicho esto (sé que puede parecer una locura), tal vez sea hora de reescribir TS en otro idioma para poder darle un impulso al sistema de tipos, ya que TS ya ha crecido tanto que es razonable pedir uno mejor.

Puede escribir fácilmente un tipo Concat<> y hacer un tipo Concat3<> , usando Concat<> .

¿Podría proporcionar una implementación para el tipo Concat<> que está describiendo? Es fácil escribir Cons<> , pero Concat<> no es tan fácil (para mí) y me encantaría ver lo que estás imaginando.

Con respecto a Concat3<> , Concat4<> , etc., la esperanza es que a largo plazo no necesitemos escribir docenas de variantes como estas, porque tendremos tipos variadas. 🙂 Sin embargo, si hoy es posible una buena implementación de ellos, esa sería una medida provisional razonable.

Para la concatenación regular de dos tuplas,
https://github.com/AnyhowStep/ts-trampoline-test (usa trampolines para Concat tuplas muy grandes, que la mayoría de la gente no necesitará)

Concat3 solo sería Concat, C>

El VarArgConcat sería,
VarArgConcat<TuplesT extends readonly (readonly unknown[])[], ResultT extends readonly unknown[] = []>

Si bien la tupla no está vacía, VargArgConcat<PopFront<TuplesT>, Concat<ResultT, TuplesT[0]>>

Si TuplesT está vacío, devuelve ResultT

Por supuesto, la recursividad ingenua conducirá a errores de profundidad máxima con tuplas de una longitud decente. Entonces, use la técnica de recursividad en ts-toolbelt, o use trampolines con copiar y pegar a la profundidad deseada


Ese repositorio al que me vinculé usa Reverse<> para implementar Concat<> . Copié y pegué el código de otro proyecto en el que estoy trabajando.

Estoy de acuerdo en que esta sería una característica tremendamente útil.

Digamos que tenemos un tipo T :

type T = {
  tags: ["a", "b", "c"];
};

Y queremos crear un nuevo tipo, con una etiqueta adicional "d" , agregada a la tupla T["tags"] . Los usuarios inicialmente pueden intentar crear esta utilidad ( WithTag<NewTag, ApplyTo> ) de la siguiente manera:

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
  tags: [Tag, ...Target["tags"]];
};

Intentar esto arroja actualmente el error A rest element type must be an array type . Los usuarios pueden pensar que cambiar string[] por Array<string> hace una diferencia, pero no es así. Tampoco el uso de una condición + never :

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
- tags: [Tag, ...Target["tags"]];
+ tags: Target["tags"] extends string[] ? [Tag, ...Target["tags"]] : never;
};

Zona de juegos Enlace: https://www.typescriptlang.org/play?#code/C4TwDgpgBA6glsAFgFQIYHMA8AoKU3pQQAewEAdgCYDOU1wATnOegDS76oPoTBGkUaUAN7AM1AFx1GzdAG0AugF9sAPigBeTt15QAZCI5j0kqHIKsoAOhtodwOQCJj1RwoUBubEq -ZQkKABJTUM8FyknVEdLRwAjaKhHAGM3Lx9sP3BoZBD4JAJMR0oEwNVfAHoAKkrcSqgAUWJIJLJKKAADZHaoYAB7KFjoXoAzHsRoYd6AGynegHdZHqyrWqhV4VWe8QjHKJj4mJSY4s9VlShK8qA

Problemas relacionados ::

Desafortunadamente, esta estrategia no funciona para los parámetros de reposo. Estos simplemente se convierten en matrices:

function i(a: number, b?: string, ...c: boolean[]): number {
}
let curried = curry(i, 12);
curried('foo', [true, false]);
curried([true, false]);

Aquí, curry: ...([string, boolean[]] | [boolean[]]) => number .
Creo que esto podría ser compatible si hubiera un caso especial para funciones con un parámetro de resto de tupla, donde el último elemento de la tupla es una matriz.
En ese caso, la llamada a la función permitiría que los argumentos adicionales del tipo correcto coincidan con la matriz.
Sin embargo, eso parece demasiado complejo para que valga la pena.

Esto tiene dos problemas:

  1. El código es incorrecto. curried() no acepta una matriz. Llenar el parámetro c rest con [true, false] podría hacerse con curried('foo', ...[true, false]) pero esto fallará en TypeScript con esta sugerencia. Es posible que no podamos proporcionar una solución de mecanografía en algunos casos, ¡pero se desaconseja proporcionar a alguien incorrecto!
  2. Sin querer, combinó parámetros opcionales y de descanso y reveló un error con su propuesta. curried() no se puede llamar sin b pero con c . Hacerlo conducirá a una mala conducta. TypeScript sabe que curried() es (...items: [string, boolean[]] | [boolean[]]) pero eso no es cierto . Debido a que JavaScript no requiere escritura, pasar [true, false] a c (asumiendo que resolvimos el problema anterior) con curried([true, false]) no establecerá b en undefined (o su valor predeterminado) y c a [true, false] , pero establecerá b en true y c en [false] !

Sugiero las siguientes correcciones:

  1. Para el segundo (y más fácil) problema, la solución es simple: no infiera una tupla de unión para el último argumento opcional (es decir, [number, string, boolean[]] | [number, boolean[]] en nuestro caso) cuando hay un parámetro de descanso. En su lugar, infiera [number, string, boolean[]] | [number] , es decir, un caso para la firma completa, incluidos todos los opcionales y el resto, uno para cada opcional excepto el último , y uno sin el último y el resto.
  2. El primer problema es más complicado: ya ha dicho que cree que es demasiado complejo para que valga la pena. Creo que vale la pena dada la popularidad de los parámetros de descanso, pero es necesario debido al primer problema (¡victoria! 😄). Creo que sería bueno si exponemos la interfaz para tuple-with-last-rest-array (pienso en la sintaxis [t1, t2, t3, ...arr] ), pero no es necesario. Podemos quedarnos con él como interno (jaja, aún tendrás que lidiar con cómo mostrar el tipo en un IDE 😈).

Pero después de todas las quejas y provocaciones, ¡gran propuesta! Gracias 👍 (solo para tranquilizarte, este es el primer problema en GitHub al que respondí con tres emojis: 👍, 🎉 y ❤️).

Esto sería realmente útil en el inyector angular que actualmente tiene que usar any https://github.com/angular/angular/issues/37264

En este ejemplo, A, B, C podrían representarse como un solo tipo genérico ...A variadic. Pero no tengo idea de cómo esto se correlacionaría con algo donde cada elemento del genérico variadic estaría encerrado en otro tipo ( Type ). tal vez con un tipo de ayudante? ¿O la sintaxis debería permitir algo como ...Type<A> ?

export declare interface TypedFactoryProvider<T, A, B, C> {
    provide: Type<T | T[]> | InjectionToken<T | T[]>;
    multi?: boolean;
    useFactory: (a: A, b: B, c: C) => T;
    deps: [Type<A>, Type<B>, Type<C>];
}

(contexto: una implementación de Provider inyectaría instancias de deps en esa función de fábrica en ese orden. Una escritura estricta garantizaría que el desarrollador sepa qué se inyectaría y en qué orden).

Cuando haya terminado, recuerde actualizar el segundo parámetro de String.prototype.replace, para que finalmente tenga la escritura adecuada en Typecript.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter

@Griffork Te das cuenta de que sería necesario analizar la expresión regular para averiguar cuántos grupos de captura tiene, ¿verdad?

Esto sería realmente útil en el inyector angular que actualmente tiene que usar any angular / angular # 37264

En este ejemplo, A, B, C podrían representarse como un solo tipo genérico ...A variadic. Pero no tengo idea de cómo esto se correlacionaría con algo donde cada elemento del genérico variadic estaría encerrado en otro tipo ( Type ). tal vez con un tipo de ayudante? ¿O la sintaxis debería permitir algo como ...Type<A> ?

export declare interface TypedFactoryProvider<T, A, B, C> {
  provide: Type<T | T[]> | InjectionToken<T | T[]>;
  multi?: boolean;
  useFactory: (a: A, b: B, c: C) => T;
  deps: [Type<A>, Type<B>, Type<C>];
}

(contexto: una implementación de Provider inyectaría instancias de deps en esa función de fábrica en ese orden. Una escritura estricta garantizaría que el desarrollador sepa qué se inyectaría y en qué orden).

@AlexAegis

Siento que se escribiría algo como esto:

export declare interface TypedFactoryProvider<T, ...P> {
  provide: Type<T | T[]> | InjectionToken<T | T[]>;
  multi?: boolean;
  useFactory: (...providers: ...P) => T;
  deps: [...Type<P>];
}

Este problema ahora está solucionado por # 39094, programado para TS 4.0.

Si esto viene con 4.0, ahora tenemos una razón para nombrarlo 4.0 😃
Esta es realmente una característica nueva importante 🎉

¡Esto es genial! Lo único que "queda" es el mismo para los tipos de cadenas literales

@sandersn Estoy tratando de pensar en cómo se usaría esta sintaxis en cosas como RxJS , donde los parámetros del método pipe son dependientes entre sí,

como en pipe(map<T, V>(...), map<V, U>(...), filter(...), ...) . ¿Cómo lo escribiría de una manera que no es lo que hacen ahora? (Las docenas de líneas de diferentes longitudes variadas escribiendo)

@gioragutt usando el PR que @ahejlsberg envió, creo que esto funcionaría, pero podría estar equivocado 😄

type Last<T extends readonly unknown[]> = T extends readonly [...infer _, infer U] ? U : undefined;

interface UnaryFunction<T, R> { (source: T): R; }

type PipeParams<T, R extends unknown[]> = R extends readonly [infer U] ? [UnaryFunction<T, U>, ...PipeParams<R>] : [];

function pipe<T, R extends unknown[]>(...fns: PipeParams<T, R>): UnaryFunction<T, Last<R>>;

@tylorr No funciona del todo debido a un error de tipo circular.

Sin embargo, funciona la solución habitual .

type Last<T extends readonly unknown[]> = T extends readonly [...infer _, infer U] ? U : undefined;

interface UnaryFunction<T, R> { (source: T): R; }

type PipeParams<T, R extends unknown[]> = {
    0: [],
    1: R extends readonly [infer U, ...infer V]
    ? [UnaryFunction<T, U>, ...PipeParams<U, V>]
    : never
}[R extends readonly [unknown] ? 1 : 0];

declare function pipe<T, R extends unknown[]>(...fns: PipeParams<T, R>): UnaryFunction<T, Last<R>>;

@isiahmeadows Eso no parece funcionar para mí. 😢
Ejemplo de parque infantil .

Tengo algo más cercano a trabajar, pero no deduciré los tipos.
ejemplo de patio de recreo

Tuve que cambiar
R extends readonly [unknown] ? 1 : 0
para
R extends readonly [infer _, ...infer __] ? 1 : 0

No estoy seguro de por qué

@tylorr @treybrisbane Podría estar relacionado: https://github.com/microsoft/TypeScript/pull/39094#issuecomment -645730082

Además, en cualquier caso, recomiendo encarecidamente compartir eso en la solicitud de extracción en la que se encuentra el comentario.

Los tipos de tuplas variables son una adición increíble al lenguaje, ¡gracias por el esfuerzo!

Parece que construcciones como curry también podrían beneficiarse (recién probado con el patio de pruebas):

// curry with max. three nestable curried function calls (extendable)
declare function curry<T extends unknown[], R>(fn: (...ts: T) => R):
  <U extends unknown[]>(...args: SubTuple<U, T>) => ((...ts: T) => R) extends ((...args: [...U, ...infer V]) => R) ?
    V["length"] extends 0 ? R :
    <W extends unknown[]>(...args: SubTuple<W, V>) => ((...ts: V) => R) extends ((...args: [...W, ...infer X]) => R) ?
      X["length"] extends 0 ? R :
      <Y extends unknown[]>(...args: SubTuple<Y, X>) => ((...ts: X) => R) extends ((...args: [...Y, ...infer Z]) => R) ?
        Z["length"] extends 0 ? R : never
        : never
      : never
    : never

type SubTuple<T extends unknown[], U extends unknown[]> = {
  [K in keyof T]: Extract<keyof U, K> extends never ?
  never :
  T[K] extends U[Extract<keyof U, K>] ?
  T[K]
  : never
}

type T1 = SubTuple<[string], [string, number]> // [string]
type T2 = SubTuple<[string, number], [string]> // [string, never]

const fn = (a1: number, a2: string, a3: boolean) => 42

const curried31 = curry(fn)(3)("dlsajf")(true) // number
const curried32 = curry(fn)(3, "dlsajf")(true) // number
const curried33 = curry(fn)(3, "dlsajf", true) // number
const curried34 = curry(fn)(3, "dlsajf", "foo!11") // error

Sin embargo, la función genérica no funciona con el curry anterior.

No creo que este PR resuelva este problema en particular, por cierto.

Con el PR esto funciona

function foo<T extends any[]>(a: [...T]) {
  console.log(a)
}

foo<[number, string]>([12, '13']);

Pero este problema me gustaría ver una implementación para esto, por lo que veo:

function bar<...T>(...b: ...T) {
  console.log(b)
}

bar<number, string>(12, '13');

Hay muchos corchetes angulares allí, parece un poco redundante.

@AlexAegis No estoy seguro de ver mucho valor en "parámetros de tipo de descanso" como ese. Ya puedes hacer esto:

declare function foo<T extends any[]>(...a: T): void;

foo(12, '13');  // Just have inference figure it out
foo<[number, string]>(12, '13');  // Expclitly, but no need to

No crea que realmente queremos un concepto completamente nuevo (es decir, parámetros de tipo de descanso) solo para evitar los corchetes en los raros casos en los que la inferencia no puede resolverlo.

@ahejlsberg ya veo. Estaba preguntando porque algunas bibliotecas (RxJS como se mencionó) usaban soluciones para proporcionar esta funcionalidad. Pero es finito.

bar<T1>(t1: T1);
bar<T1, T2>(t1: T1, t2:T2);
bar<T1, T2, T3>(t1: T1, t2:T2, t3: T3, ...t: unknown) { ... }

Así que ahora se quedan con eso o hacen que los usuarios escriban los corchetes, lo cual es un cambio rotundo y no tan intuitivo.

La razón por la que utilicé este ejemplo es porque aquí es sencillo que definí el tipo de esa tupla. Un corchete aquí, otro allá

foo<[number, string]>([12, '13']);

Aquí no es tan obvio que la tupla se refiera a ese parámetro de descanso si lo miras desde afuera

foo<[number, string]>(12, '13'); 

Pero sí, como dijiste, si dejamos que la inferencia lo resuelva, entonces estos casos triviales no requieren ninguna modificación por parte del usuario. Pero no sabemos si los configuraron explícitamente o no, depende de ellos, por lo que aún cuenta como un cambio importante. Pero esa es la preocupación de la biblioteca y no de este cambio.

Dicho esto, me parece extraño que si hay parámetros de descanso, definidos desde el exterior uno por uno, que son una única matriz en el interior diferenciada por ... , no se puedan hacer genéricos de la misma manera: uno por uno en el exterior, una única matriz en el interior, diferenciada por ... .

Las discrepancias de sintaxis menores realmente no valen el costo de soporte para un
amable. El uso de tipos sería una decisión de diseño correcta cuando TS estaba planeando
soporte para parámetros de descanso, pero supongo que ahora podría conducir a más
confusión tanto para los desarrolladores de idiomas como para los usuarios. Necesitábamos una solución para
este problema, y ​​Anders hizo su trabajo excepcionalmente bien evitando que
complejidad al ceñirse a [...T] lugar de T . ¡Felicitaciones!

(¿Podríamos ahora echar un vistazo a un error que unifica el tipo de intersección
la variable inferida en tipo condicional devuelve el tipo de intersección más a la derecha
argumento, o esa unión de matrices no es matriz de unión, por favor? Aún
tienen grandes showstoppers en el sistema de tipos.)

El viernes 19 de junio de 2020 a las 10:41, Győri Sándor [email protected] escribió:

@ahejlsberg https://github.com/ahejlsberg Ya veo. Estaba preguntando porque
algunas bibliotecas (RxJS como se mencionó) utilizaron soluciones para proporcionar este
funcionalidad. Pero es finito.

bar(t1: T1); barra(t1: T1, t2: T2); barra(t1: T1, t2: T2, t3: T3, ... t: desconocido) {...}

Así que ahora se quedan con eso o hacen que los usuarios escriban los corchetes,
que no es tan intuitivo.

La razón por la que utilicé este ejemplo es porque aquí es sencillo
que definí el tipo de esa tupla. Un corchete aquí, otro allá

foo <[número, cadena]> ([12, '13']);

Aquí no es tan obvio que la tupla se refiera a ese parámetro de descanso si
lo miras desde afuera

foo <[número, cadena]> (12, '13');

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646490130 ,
o darse de baja
https://github.com/notifications/unsubscribe-auth/AAWYQIMTTB6JEPSQFUMTMDTRXMJD5ANCNFSM4BTBQ7DQ
.

Por supuesto, no estoy ni cerca de su calibre, pero respetuosamente no estoy de acuerdo con @ahejlsberg .
En mi experiencia, gran parte de la complejidad de la escritura mecanografiada proviene del hecho de que muchas características (interesantes y útiles, sin duda) se clasifican de manera especial como conceptos propios.

¡Sin embargo, esta complejidad no es inherentemente una función del número de características!
En cambio, el lenguaje podría diseñarse en torno a conceptos más amplios y generales de los cuales estos casos especiales podrían luego deducirse trivialmente o implementarse en la biblioteca std (tipo).

El concepto más general de este tipo sería, por supuesto, implementar completamente los tipos dependientes, de los cuales todo lo demás podría derivarse, pero no es necesario llegar tan lejos:
Como han demostrado C ++ y, en menor medida, Rust, algunos conceptos consistentes a gran escala le brindan un montón de características de forma gratuita.
Esto es similar a lo que OCaml y Haskell (¿y supongo que F #?) Han hecho en el nivel de valor, solo en el nivel de tipo.

La programación de nivel de tipo no es nada de lo que temer siempre que esté diseñada en el lenguaje en lugar de agregarse para proporcionar características específicas.
Las facilidades en C ++ 14/17 son muy intuitivas excepto por su sintaxis, que se debe puramente al bagaje histórico.

Se podrían haber agregado conceptos generales en el diseño original. Después del diseño
ya se cometió un error, no se puede agregar consistencia sin arriesgar enormes
Incompatibilidad hacia atrás. Estoy de acuerdo con las sospechas sobre el diseño del lenguaje como
un todo (TS está bastante lejos de los estándares establecidos por la academia, nadie puede
no estoy de acuerdo con eso). Hay muchos errores e inconsistencias que son
fundamental para millones de bases de código de producción. Simple hecho de que
los desarrolladores pueden aportar adiciones útiles al lenguaje
sin corregir accidentalmente esos errores es, en mi humilde opinión, increíble
y merece respeto. TS tiene las mismas complejidades de diseño que C ++ aquí, pero su
El sistema de tipos expresivos empeora la situación.

El viernes 19 de junio de 2020 a las 12:47, Bennett Piater [email protected] escribió:

Por supuesto, no estoy ni cerca de su calibre, pero respetuosamente no estoy de acuerdo.
con @ahejlsberg https://github.com/ahejlsberg .
En mi experiencia, gran parte de la complejidad del texto mecanografiado proviene delhecho de que muchas características (interesantes y útiles sin duda) sonespecial-encajonado como sus propios conceptos.

Esta complejidad no es inherentemente función del número de características
¡aunque!
En cambio, el lenguaje podría diseñarse en torno a un lenguaje más amplio y global.
conceptos a partir de los cuales estos casos especiales podrían entonces deducirse trivialmente, o
implementado en la biblioteca std (tipo).

El concepto más general de este tipo sería, por supuesto, implementar completamente
tipos dependientes, de los cuales todo lo demás podría entonces derivarse, pero
ir tan lejos no es necesario:
Como C ++ y, en menor medida, Rust han mostrado, algunos a gran escala,
Los conceptos consistentes le brindan un montón de funciones de forma gratuita.
Esto es similar a lo que OCaml y Haskell (¿y supongo que F #?) Han hecho en
el nivel de valor, solo en el nivel de tipo.

La programación de nivel de tipo no es nada de lo que temer mientras sea
diseñado en el lenguaje en lugar de agregarse para proporcionar información específica
características.
Las instalaciones en C ++ 14/17 son muy intuitivas excepto por su sintaxis,
que se debe exclusivamente al bagaje histórico.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646543896 ,
o darse de baja
https://github.com/notifications/unsubscribe-auth/AAWYQIMWYLGGCWPTDBZJR4TRXMX4RANCNFSM4BTBQ7DQ
.

@ polkovnikov-ph Me alegro de que estemos de acuerdo en el tema que nos ocupa :)

En cuanto a la solución, creo que todavía valdría la pena considerar avanzar progresivamente hacia un sistema de tipos diseñado con más cuidado. Las versiones principales son una cosa después de todo, y la alternativa es terminar en el clúster * * que es C ++ 20, un intento admirable de agregar características aún más bien diseñadas además de 2 capas de intentos anteriores que no se pueden eliminar, en un sintaxis que ya no se puede analizar de forma determinista.

Todo esto está fuera del tema de este hilo y se está discutiendo aquí . Así que intentaré ser franco:

La academia tardó décadas en descubrir el enfoque correcto para la subtipificación: el sistema de tipos mlsub se creó hace solo 6 años, mucho después del lanzamiento de TypeScript. Podría ser esa base para clases, interfaces, tipos de unión e intersección con conceptos generales.

Pero recuerde también que hay tipos condicionales. No tengo conocimiento de ningún artículo que les dé semántica formal o que describa un sistema de tipo mínimo con tipos condicionales con pruebas de progreso / preservación. Creo que eso podría tener algo que ver con los científicos que aún se muestran tímidos a la hora de publicar sus intentos fallidos. Si su propuesta asume que esas principales versiones incompatibles se realizarán en la década de 2040, cuando la academia se sienta cómoda con los tipos condicionales, estoy de acuerdo.

De lo contrario, un "sistema de tipos cuidadosamente diseñado" tendría que eliminar los tipos condicionales del lenguaje, y no creo que nadie esté a la altura de la tarea de convertir el 60% de DefinitelyTyped para usar cualquier alternativa elegida para reemplazarlos. (Y luego hágalo varias veces más, porque no es el único problema).

Me temo que la única solución viable es crear un lenguaje de programación separado que de alguna manera se parezca a TS, y de alguna manera (no solo por ser más placentero escribir código) atraer a los desarrolladores para que lo usen. Ryan fue bastante elocuente recomendando este enfoque para la mejora del TS anteriormente.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

kyasbal-1994 picture kyasbal-1994  ·  3Comentarios

Roam-Cooper picture Roam-Cooper  ·  3Comentarios

jbondc picture jbondc  ·  3Comentarios

weswigham picture weswigham  ·  3Comentarios

wmaurer picture wmaurer  ·  3Comentarios