Typescript: Proposta: Tipos Variadic - Dê tipos específicos para funções variadic

Criado em 29 out. 2015  ·  265Comentários  ·  Fonte: microsoft/TypeScript

Tipos Variadic

Dê Tipos Específicos para Funções Variadic

Esta proposta permite que o Typescript forneça tipos para funções de ordem superior que usam um número variável de parâmetros.
Funções como essa incluem concat , apply , curry , compose e quase qualquer decorador que envolva uma função.
Em Javascript, espera-se que essas funções de ordem superior aceitem funções variáveis ​​como argumentos.
Com os padrões ES2015 e ES2017, esse uso se tornará ainda mais comum à medida que os programadores começarem a usar argumentos de propagação e parâmetros de descanso para matrizes e objetos.
Esta proposta aborda esses casos de uso com uma única estratégia de tipagem muito geral baseada em tipos de ordem superior.

Esta proposta resolveria total ou parcialmente vários problemas, incluindo:

  1. # 5331 - Tuplas como tipos para resto ... argumentos
  2. # 4130 - O compilador relata incorretamente a incompatibilidade de assinatura de parâmetro / destino de chamada ao usar o operador de propagação
  3. # 4988 - As tuplas devem ser clonáveis ​​com Array.prototype.slice ()
  4. # 1773 - Genéricos variáveis?
  5. # 3870 - Tipos de descanso em genéricos para tipos de interseção.
  6. # 212 - bind, call e apply não têm tipo (requer # 3694 os tipos desta função).
  7. # 1024 - Digitado ... parâmetros restantes com genéricos

Estarei atualizando esta proposta no meu fork do Typescript-Handbook: sandersn / TypeScript-Handbook @ 76f5a75868de3fb1ad4dbed5db437a8ab61a2698
Tenho uma implementação em andamento em sandersn / TypeScript @ f3c327aef22f6251532309ba046874133c32f4c7 que atualmente tem as partes simples da proposta implementadas.
Ele substitui a parte 2 da minha proposta anterior, # 5296.
Editar: Adicionada uma seção sobre atribuibilidade. Não tenho mais certeza de que substitui estritamente # 5296.

Visualize o exemplo com curry

curry para funções com dois argumentos é simples de escrever em Javascript e Typescript:

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

e em Typescript com anotações de tipo:

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

No entanto, uma versão variável é fácil de escrever em Javascript, mas não pode receber um tipo em TypeScript:

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

Aqui está um exemplo de uso de tipos variáveis ​​desta proposta para digitar curry :

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

A sintaxe para tipos de tuplas variáveis ​​que utilizo aqui corresponde à sintaxe de propagação e repouso usada para valores em Javascript.
Isso é mais fácil de aprender, mas pode dificultar a distinção entre anotações de tipo e expressões de valor.
Da mesma forma, a sintaxe para concatenar se parece com a construção de tupla, embora seja realmente uma concatenação de dois tipos de tupla.

Agora vamos ver um exemplo de chamada para 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')();

Na primeira chamada,

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

Na segunda chamada,

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

Sintaxe

A sintaxe de uma variável de tipo variável é ...T onde _T_ é um identificador que é, por convenção, uma única letra maiúscula, ou T seguido por um identificador PascalCase .
Variáveis ​​de tipo variável podem ser usadas em vários contextos sintáticos:

Os tipos variáveis ​​podem ser vinculados no local usual para a vinculação do parâmetro de tipo, incluindo funções e classes:

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

E eles podem ser referenciados em qualquer local de anotação de tipo:

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];
}

Variáveis ​​de tipo variável, como variáveis ​​de tipo, são bastante opacas.
Eles têm uma operação, ao contrário das variáveis ​​de tipo.
Eles podem ser concatenados com outros tipos ou com tuplas reais.
A sintaxe usada para isso é idêntica à sintaxe de propagação de tupla, mas no local da anotação de tipo:

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

Os tipos de tupla são instâncias de tipos variados, então eles continuam a aparecer sempre que as anotações de tipo eram permitidas anteriormente:

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

Uma variável de tipo variável representa um tipo de tupla de qualquer comprimento.
Uma vez que representa um conjunto de tipos, usamos o termo 'tipo' para nos referir a ele, seguindo seu uso na teoria dos tipos.
Como o conjunto de tipos que representa são tuplas de qualquer comprimento, qualificamos 'tipo' com 'variável'.

Portanto, declarar uma variável do tipo de tupla variável permite que ela assuma qualquer tipo de tupla _única_.
Assim como as variáveis ​​de tipo, as variáveis ​​de tipo só podem ser declaradas como parâmetros para funções, classes, etc, o que permite que sejam usadas dentro do corpo:

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

Chamar uma função com argumentos digitados como um tipo variável atribuirá um tipo de tupla específico ao tipo:

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

Atribui o tipo de tupla ...T=[number,number,string] ... T . So in this application of f , deixe a: ... T is instantiated as deixe a: [número, número, string] . 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` é permitido.
Por exemplo, novos elementos podem ser adicionados à 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]));

Como as variáveis ​​de tipo, as variáveis ​​de tipo variadico geralmente podem ser inferidas.
As chamadas para cons poderiam ter sido anotadas:

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

Por exemplo, cons deve inferir duas variáveis, um tipo _H_ e um tipo _... Tail_.
Na chamada mais interna, cons("foo", ["baz", false]) , H=string e ...Tail=[string,boolean] .
Na chamada externa, H=number e ...Tail=[string, string, boolean] .
Os tipos atribuídos a _... Tail_ são obtidos digitando literais de lista como tuplas - variáveis ​​de um tipo de tupla também podem ser usadas:

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

Além disso, as variáveis ​​de tipo variável podem ser inferidas quando concatenadas com tipos:

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

Aqui, o tipo de l é inferido como [number, string, boolean] .
Então H=number e ...Tail=[string, boolean] .

Limites na inferência de tipo

Os tipos concatenados não podem ser inferidos porque o verificador não consegue adivinhar onde deveria estar o limite entre os dois tipos:

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

O verificador não pode decidir se atribui

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

Algumas ligações inequívocas são uma casualidade desta restrição:

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

A solução é adicionar anotações de tipo:

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

Dependências não verificáveis ​​entre os argumentos de tipo e o corpo da função podem surgir, como em 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 função pode ser digitada, mas há uma dependência entre n e as variáveis ​​de tipo: n === ...T.length deve ser verdadeiro para que o tipo seja correto.
Não tenho certeza se esse é um código que realmente deveria ser permitido.

Semântica em classes e interfaces

A semântica é a mesma em classes e interfaces.

TODO: Provavelmente, existem algumas rugas específicas de classe na semântica.

Atribuições entre tuplas e listas de parâmetros

Tipos de tupla podem ser usados ​​para fornecer um tipo para argumentos restantes de funções dentro de seu escopo:

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']);

Neste exemplo, a lista de parâmetros de f: (a: number, b:string) => string deve ser atribuível ao tipo de tupla instanciado para o tipo ...T .
O tipo de tupla inferido é [number, string] , o que significa que (a: number, b: string) => string deve ser atribuível a (...args: [number, string]) => string .

Como efeito colateral, as chamadas de função serão capazes de tirar proveito dessa capacidade de atribuição, espalhando tuplas em parâmetros de descanso, mesmo se a função não tiver um tipo de tupla:

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

Tipos de tupla gerados para parâmetros opcionais e de descanso

Como as tuplas não podem representar parâmetros opcionais diretamente, quando uma função é atribuída a um parâmetro de função que é tipado por um tipo de tupla, o tipo de tupla gerado é uma união de tipos de tupla.
Observe o tipo de h depois de 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

Aqui ...T=([number] | [number, string]) , então curried: ...([number] | [number, string]) => number que pode ser chamado como você esperaria. Infelizmente, essa estratégia não funciona para os parâmetros de descanso. Eles simplesmente são transformados em matrizes:

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

Aqui, curried: ...([string, boolean[]] | [boolean[]]) => number .
Eu acho que isso poderia ser suportado se houvesse um caso especial para funções com um parâmetro de resto da tupla, onde o último elemento da tupla é uma matriz.
Nesse caso, a chamada de função permitiria que argumentos extras do tipo correto correspondessem ao array.
No entanto, isso parece muito complexo para valer a pena.

Extensões para outras partes do texto datilografado

  1. O typescript não permite que os usuários escrevam um tipo de tupla vazia.
    No entanto, esta proposta requer que os tipos variáveis ​​sejam vinculáveis ​​a uma tupla vazia.
    Portanto, o Typescript precisará oferecer suporte a tuplas vazias, mesmo que apenas internamente.

    Exemplos

Muitos desses exemplos são possíveis como funções de argumento fixo no Typecript atual, mas com esta proposta eles podem ser escritos como variáveis.
Alguns, como cons e concat , podem ser escritos para arrays homogêneos no Typescript atual, mas agora podem ser escritos para tuplas heterogêneas usando tipos de tupla.
Isso segue mais de perto a prática típica de Javascript.

Retorna um 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"

Funções variáveis ​​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: f poderia retornar ...U vez 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);
    }
}

Perguntas abertas

  1. A história de atribuibilidade da tupla à lista de parâmetros se mantém? É especialmente instável em torno dos parâmetros opcionais e de descanso.
  2. O tipo inferido será uma união de tuplas como no caso de parâmetro opcional? Como bind , call e apply são métodos definidos em Function, seus argumentos de tipo precisam ser vinculados no momento da criação da função, em vez de bind call site (por exemplo). Mas isso significa que funções com sobrecargas não podem receber ou retornar tipos específicos para seus argumentos - elas precisam ser uma união dos tipos de sobrecarga. Além disso, Function não tem um construtor que especifica os argumentos de tipo diretamente, então realmente não há como fornecer os tipos corretos para bind et al. TODO: Adicione um exemplo aqui. Observe que esse problema não é necessariamente exclusivo das funções variáveis.
  3. Os parâmetros de resto devem ser especiais para manter sua sintaxe de chamada agradável, mesmo quando eles são gerados a partir de um tipo de tupla? (Nesta proposta, as funções digitadas por um tipo de tupla precisam passar matrizes para seus parâmetros restantes, elas não podem ter parâmetros extras.)
Fix Available In Discussion Suggestion

Comentários muito úteis

Esse problema agora foi corrigido pelo número 39094, previsto para TS 4.0.

Todos 265 comentários

+1, isso é realmente útil para programação funcional em TypeScript! Como isso funcionaria com argumentos opcionais ou restantes? Mais concreto, a função compose ser usada em funções com argumentos restantes ou argumentos opcionais?

Bom ponto. Acho que você poderia atribuir o menor tipo de tupla permitido a uma função opcional-param, já que tuplas são apenas objetos, que permitem membros adicionais. Mas isso não é o ideal. Vou ver se consigo descobrir o exemplo compose e depois atualizarei a proposta.

Na verdade, os tipos de sindicato provavelmente funcionariam melhor. 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

Isso ainda quebra os parâmetros de descanso, no entanto.

@ahejlsberg , você tinha algumas idéias de como os tipos de tupla funcionariam, eu acho.

Então: +1: sobre isso. Para obter informações, isso está relacionado (e cumpriria) o nº 3870. Tentamos implementar uma API de composição de tipo no TypeScript, mas estamos tendo que contornar algumas das limitações observadas nesta proposta. Isso certamente resolveria alguns desses problemas!

No entanto, parece que às vezes você pode querer "mesclar" esses tipos de tupla em vez de persisti-los, especialmente com algo como compor. Por exemplo:

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

Além disso, em muitos de seus exemplos, você tem usado primitivas. Como você veria algo mais complexo funcionando, especialmente se houver conflitos?

Infelizmente, esta proposta no estado em que se encontra não aborda # 3870 ou a composição de tipo, uma vez que o único operador de composição para tipos de tupla é [T,...U] . Você também pode escrever isso como T + ...U (que é mais indicativo do que acontece com os tipos), mas # 3870 e sua biblioteca de composição de tipos precisam de T & ...U . Eu acho que pode ser possível, mas eu preciso entender @JsonFreeman 's e @jbondc' s idéias de # 3870 em primeiro lugar. Expandirei a proposta se conseguir descobrir como ela deve funcionar.

Observação: decidi usar a sintaxe [...T, ...U] porque ela se parece com a sintaxe de difusão de valor equivalente, mas T + ...U é mais indicativo do que está acontecendo com os tipos. Se terminarmos com ambos, então + e & podem ser os operadores a serem usados.

Grande: +1: sobre isso!

+1 incrível! Isso permitiria expressar essas coisas de forma muito mais expressiva e leve.

Meu ponto em # 3870 parece ser um problema aqui. Especificamente, me preocupo em inferir argumentos de tipo para parâmetros de tipo variadic.

A inferência do argumento de tipo é um processo bastante complicado e mudou de maneiras sutis ao longo do tempo. Quando os argumentos são comparados a parâmetros para inferir argumentos de tipo de tipo, não há garantias sobre a ordem em que os candidatos são inferidos, nem quantos candidatos são inferidos (para um determinado parâmetro de tipo). Isso geralmente não tem sido um problema porque o resultado apresentado ao usuário não expõe (na maioria dos casos) esses detalhes. Mas se você fizer um tipo de tupla com os resultados da inferência, isso certamente expõe a ordem e a contagem das inferências. Esses detalhes não deveriam ser observáveis.

Quão sério é isso? Acho que depende de como exatamente a inferência funciona. Qual é o resultado do seguinte:

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

@jbondc , - parece uma boa ideia. Vou manter isso em mente, mas não explorarei aqui, porque acho que devemos introduzir novos operadores de tipo um de cada vez. Tanto & quanto + criam novos tipos, mas & cria um tipo de interseção, enquanto + cria um novo tipo de tupla (por isso prefiro a sintaxe [T,...U] vez de T + ...U , porque [T,U] já faz isso para tipos).

@JsonFreeman Acho que não há problema em fazer uma das duas coisas com parâmetros de tipo repetidos:

  1. Una os tipos: f(['hello', 1], [1, false]): [string | number, number | boolean]
  2. Não permitir a inferência de parâmetros de tipo de tupla repetidos, especialmente se a inferência de argumento de tipo for complicada. Algo assim:
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

Acho que as bibliotecas reais (como as extensões reativas às quais @Igorbek está vinculado) geralmente terão apenas um parâmetro de tipo de tupla, portanto, embora nem (1) nem (2) sejam particularmente utilizáveis, não devem impactar muito o código do mundo real.

Nos exemplos acima, curry é o mais difícil de inferir - você deve pular f: (...args:[...T,...U]) => V , inferir ...ts:...T , depois voltar e definir ...U para o que é restantes após consumir ...T dos parâmetros de f .

Comecei a fazer o protótipo disso (sandersn / TypeScript @ 1d5725d), mas ainda não fui tão longe. Alguma ideia se isso vai funcionar?

Eu erraria por não permitir qualquer coisa em que a semântica não seja clara (como inferências repetidas para o mesmo parâmetro de tipo espalhado). Isso também acalma minha preocupação acima.

Não consigo pensar em um bom mecanismo para digitar curry. Como você indicou, você deve pular a lista de parâmetros da primeira função para consumir o argumento ...T e então ver o que sobrou. Teria que haver alguma política para adiar inferências para um parâmetro de tipo espalhado se ele não for final em sua lista. Pode ficar confuso.

Dito isso, acho que vale a pena tentar. Há alta demanda para o recurso.

Eu acho que você teria que pular vários tipos de tupla que ocorrem no mesmo contexto (por exemplo, nível superior como (...T,string,...U) => V ou concatenados como [...T,...U,...T] ). Em seguida, você pode fazer várias passagens nos tipos ignorados, eliminando os tipos já inferidos e pulando novamente os tipos que ainda são ambíguos. Se em algum ponto nenhum tipo estiver disponível para inferência, pare e retorne um erro.

Então sim. Complicado.

Você pode se inspirar em um problema semelhante. Na verdade, é um pouco semelhante ao problema de inferir a uma união ou interseção. Ao inferir para um tipo de união que inclui um parâmetro de tipo que é membro do contexto de inferência, como em function f<T>(x: T | string[]) , você não sabe se deve inferir para T. A manifestação pretendida do tipo de união pode ter sido string[] . Portanto, o texto digitado primeiro infere para todos os outros constituintes e, em seguida, se nenhuma inferência foi feita, infere para T.

No caso de interseção, é ainda mais difícil porque você pode ter que dividir o tipo de argumento entre os diferentes constituintes de interseção. O typescript não faz inferências para os tipos de interseção.

E se você permitir espalhar tupla apenas se for o último tipo em sua sequência? Então [string, ...T] seria permitido, mas [...T, string] não seria?

Se bem entendi, isso realmente resolveria a história do mixin no TypeScript. Estou correto neste entendimento?

Pode ser. Você pode dar um exemplo? Não sou fluente com padrões de mixin.

A sintaxe de uma variável de tipo variadic é ... T onde T é um identificador que é por convenção uma única letra maiúscula, ou T seguido por um identificador PascalCase.

Podemos deixar o caso de um identificador de parâmetro de tipo para o desenvolvedor?

@ aleksey-bykov +1. Não vejo razão para que não seja o caso.

Os desenvolvedores com experiência em Haskell apreciariam isso.

Desculpe, essa frase pode ser analisada de forma ambígua. Eu quis dizer 'ou' para analisar bem: "por convenção (uma única letra maiúscula || T seguida por um identificador PascalCase)". Não estou propondo restringir o caso dos identificadores, apenas apontar a convenção.

Mas, pelo que vale a pena, _Eu_ tenho um histórico de Haskell e não gosto de quebrar as convenções do idioma em que estou escrevendo.

Desculpe por descarrilar. Minha última pergunta curiosa (se você não se importa que eu pergunte) qual é a "convenção" do TypeScript que pode ser quebrada e quem está preocupado?

@sandersn

Isso deve digitar check, assumindo que T & ...U significa T & U & V & ... (que é o comportamento 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)
}

Ou em um arquivo de definição:

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

@aleksey-bykov a convenção de que estou falando é o caso de identificadores de parâmetro de tipo. Quem está preocupado? Pessoas que precisam ler um novo código Typecript que nunca viram antes - as convenções ajudam os novos leitores a entender o novo código mais rápido.

@sandersn @aleksey-bykov teve a impressão de que o seguinte seria _sintaticamente_ inválido:

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

@isiahmeadows & e | operações sobre tipos não são abordadas nesta proposta, embora eu deva adicioná-los para questões abertas / trabalho futuro, caso não o tenha feito. No momento, o único operador proposto é a concatenação: [THead, ...TTail] .

Uma diferença é que a concatenação ainda produz um tipo de tupla, enquanto & e | produzem tipos de intersecção e união, respectivamente.

@sandersn Meu assign exemplo em TypeScript seria trivial de mudar com isso.

Embora:

  1. A interseção seria semelhante à concatenação, embora seja mais parecida com a concatenação de dicionários do que com a concatenação de listas. Tipos variados podem ser implementados em cima do maquinário existente lá.
  2. A união seria como uma interseção, exceto que apenas mantinha as partes comuns. Mais uma vez, os tipos variáveis ​​podem ser implementados em cima do maquinário existente.

@isiahmeadows Uma interseção não é, em geral, uma concatenação de dicionários. Isso só é verdade para uma interseção de tipos de objeto, mas não, por exemplo, uma interseção de sindicatos. As uniões também não são o mesmo que apenas assumir as propriedades que os objetos têm em comum. Os dois são mais bem caracterizados pelo conjunto de valores que os habitam.

@sandersn Estou um pouco confuso sobre a inferência de argumento de tipo com tipos variadic. O que deve ser inferido aqui?

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

O resultado é [string, number, number[]] ? Isso significaria que você teria que confiar na inferência do argumento de tipo adicionando candidatos em uma ordem da esquerda para a direita, o que não é uma suposição trivial. Também seria a primeira vez que o sistema de tipos apresenta a lista de candidatos à inferência para o usuário.

Eu sei que é uma proposta experimental / inicial, mas poderíamos discutir a sintaxe ...T para os parâmetros restantes. Do meu ponto de vista, isso realmente não funciona.
Portanto, a sintaxe proposta é:

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

vamos comparar com a sintaxe existente dos parâmetros restantes:

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

portanto, o tipo de parâmetro a que captura os argumentos restantes é number[] , para que possamos entender claramente que é um array. Por analogia, posso inferir que ...T da proposta também representa uma matriz. Mas isso não é muito óbvio.
Em seguida, digamos que possamos definir parâmetros de descanso mais restritivos:

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

Portanto, agora, ainda vemos que o tipo de a é uma tupla (que é um array).

Minha proposta é usar uma maneira mais consistente de tratar a noção de ...T para representar como uma "alguma lista ordenada abstrata de tipos". E use-o da mesma forma que usamos o operador spread:

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

Então ...a no caso de variável, é apenas 1, "1" .

Minha sintaxe para definir os parâmetros restantes por ...T notion:

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

Para mim, faz muito mais sentido.

@Igorbek Eu estive declare function f<...T>(...a: ...T); já funcionava assim. Mas não vejo declare function f(...a: [number, string]); sendo muito usado.

Para ser mais claro.

Sintaxe proposta originalmente para os parâmetros restantes:

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

Se eu puder fazer isso

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

então serei capaz de fazer isso:

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

Portanto, o tipo de a é [...T] (retornamos assim), mas o definimos como ...T na assinatura.
Poderíamos dizer que ...T e [...T] são iguais, mas não funciona no caso de variáveis.
Para variáveis:

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

Se aplicarmos o mesmo aos parâmetros de descanso padrão

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

o tipo de a é number[] (por tipo de retorno), o mesmo que foi definido na assinatura.

@isiahmeadows sim, function f(...a: [number, string]) não funciona. Acabei de desenvolver pensamentos sobre como podemos tratar os parâmetros de repouso.

Então, indo mais longe. Para definir explicitamente os parâmetros de tipo, a seguinte sintaxe foi proposta:

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

Vira para:

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

Então, isso também pode 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 é assim que meu protótipo funciona, sim. Mas não estou familiarizado o suficiente com o algoritmo de inferência de tipo para entender por que ele funciona. Em outras palavras, a questão não é se a inferência da esquerda para a direita é uma suposição _trivial_, mas sim correta. Para o caso de identidade, a resposta é sim, mas não sei se você pode construir casos em que a resposta seja não.

Você também pode trabalhar com um exemplo de um conjunto exposto de candidatos à inferência de tipo? Como eu disse, não entendo muito bem o funcionamento do algoritmo de inferência, então um exemplo me ajudaria a entender o que você quer dizer.

E ainda melhor:

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

Eu sugiro prefixar [] para o identificador de tipo para significar o resto dos parâmetros de tipo.

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

Tem 1 caractere menor que ...T e (na minha opinião) faz menos ruído visual.

@aleksey-bykov Na verdade, tenho a opinião oposta sobre isso. Ele não se encaixa na sintaxe do parâmetro rest existente, então acredito que também seja menos claro à primeira vista.

[...T] / T como um tipo de parâmetro de matriz restante parece muito melhor para mim. Mais uma vez, compare com array e seu operador sprad:

| matrizes | tipos (da proposta) | tipos (minha atualização) |
| --- | --- | --- |
| var x = [1,2] | não | T = [T1, T2] |
| [0, ...x] === [0,1,2] | [T0, ...T] === [T0, T1, T2] | [T0, ...T] === [T0, T1, T2] |
| f(x) === f([1, 2]) | não | 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> |

Da proposta

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
}

Da minha atualização

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]
}

A atualização parece melhor agora IMO. Listas para representar tipos parecem muito boas, mas mesmo Lisps digitados não vão tão longe (tipos homoicônicos, alguém?: Smile :).

Recebo o fascínio da pureza, mas também estou olhando para o aspecto pragmático. As listas também seriam relativamente fáceis de implementar sozinhas, mas não se adaptam ao resto da linguagem. É quase como as inúmeras tentativas de implementar mônadas em Java (a linguagem) ou lambdas em C - elas sempre acabam sendo incrivelmente feias e hackeadas.

@sandersn posso tentar explicar o que quero dizer expondo a lista de candidatos. A inferência do argumento de tipo gera uma lista de candidatos para cada parâmetro de tipo. Em seguida, verifica se algum candidato é um supertipo de todos os outros e, em caso afirmativo, esse candidato é o vencedor. Portanto, no seguinte exemplo:

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

Os argumentos serão digitados e, em seguida, inferidos para cada parâmetro. Dois candidatos serão gerados, a saber (string | number)[] e string[] . Mas o primeiro vencerá porque é um supertipo do segundo. E, como resultado, o usuário nunca observa que string[] já esteve na foto. Há uma inferência para T e todos os outros candidatos são invisíveis. Isso significa que há duas coisas invisíveis para o usuário, a saber, a ordem dos candidatos e a multiplicidade dos candidatos.

Aqui está um problema com as multiplicidades se você confiar na lista de candidatos como sua lista de elementos na tupla denotada por ...T :

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

Acho que você gostaria de inferir [number, number] para T, considerando a intenção de sua proposta da forma como a entendo. Mas por causa da linha contém o cheque https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L6256 , o candidato number será adicionado apenas uma vez, e T será inferido como [number] . Esse é o problema da multiplicidade de que estava falando.

Quanto ao pedido, é da esquerda para a direita. Mas existem várias passagens e os argumentos serão reprocessados ​​se contiverem expressões de função que serão digitadas contextualmente. Se houver n argumentos contendo expressões de função contextualmente digitadas, então haverá n + 1 passagens sobre os argumentos. Um exemplo é Array.prototype.reduce, onde o parâmetro initialValue é efetivamente digitado e inferido antes do retorno de chamada, apesar de estar à direita. Portanto, algo como o seguinte pode ser um problema para a proposta:

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

Intuitivamente, T deve ser [(x: any) => any, number] , mas se você confiar na ordem em que os candidatos são adicionados, será [number, (x: any) => any] . Isso ocorre porque a inferência do argumento de tipo geralmente é da esquerda para a direita, mas as funções sujeitas à digitação contextual são adiadas para o final.

Tanto a multiplicidade quanto os problemas de ordem que expliquei são exemplos de apresentação da lista de candidatos. @ahejlsberg certamente será uma boa pessoa para perguntar sobre isso também e, de fato, ele pode ajudar a explicar, confirmar ou refutar qualquer coisa que eu disse.

@JsonFreeman, por que você acha que isso seria um problema?
Pode ser implementado virtualmente introduzindo tipos genéricos extras para cada argumento factual restante e inferindo contra função com comprimento de parâmetros fixos.
Por exemplo,

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]

A propósito, podemos inferir que x => x é do tipo { <T>(x: T): T; } ?

@Igorbek Acho que sua sugestão sobre parâmetros de tipo de manufatura (pelo menos como intuição, independentemente de como é implementado) é a maneira correta de fazê-lo. Você poderia inferir uma sequência de tipos para T , onde cada elemento na sequência tem um índice e uma lista de candidatos (esta é uma maneira alternativa de implementar o que você mencionou).

No entanto, meu ponto é, não acho que isso seja o que aconteceria naturalmente se você apenas redefinisse a lista de candidatos de inferência como a tupla inferida. Isso exigiria uma mecânica explícita para fazer a coisa certa acontecer.

Para seu ponto sobre { <T>(x: T): T; } , isso não generaliza bem para digitar coisas como x => foo(x) onde foo é alguma função. Você precisaria saber o tipo de x para fazer a resolução de sobrecarga para foo .

Um pequeno passo fora da batalha com as regras de inferência do verificador de tipo.
Tenho um comentário / sugestão sobre a sintaxe. Acho que existem duas opções consistentes, mas mutuamente exclusivas:

1. Argumentos de tipo de descanso formal

Se escolhermos este formulário:

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

então devemos usá-lo 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]
...

Assim, serão verdadeiros tipos formais de descanso. Eles devem ser usados ​​apenas na última posição da seção de parâmetro de tipo formal.

2. Argumentos rest digitados em tupla

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

Nesse caso, sempre temos um número fixo de slots na seção de parâmetro de tipo formal.

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

inferimos que T deve ser um tipo de tupla se qualquer uma das condições for atendida:

  1. T é usado para parâmetros de descanso como (... args: T) => T
  2. T é usado na composição de propagação como [... T] ou [número, ... T, string]

Assim, não precisamos usar reticências na seção de parâmetro de tipo formal (podemos inferir até _sintaticamente_ sem qualquer verificador de tipo)

neste caso, podemos escrever também

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

mas é redundante.

Pessoalmente, gostaria de ver o último implementado no TypeScript. @JsonFreeman , @sandersn?

@Artazor Acho que tudo se resume à expressividade, e não acho que as duas abordagens sejam necessariamente equivalentes. O segundo inclui a capacidade de espalhar um parâmetro de tipo de repouso dentro de um tipo de tupla, enquanto o primeiro não parece.

Acho que para referências de tipo genérico, é apenas uma questão de decidir onde e sintaticamente como usar um parâmetro de tipo rest. Isso precisaria ser decidido para todos os construtores de tipo que usam uma sequência de tipo (tuplas, assinaturas, referências de tipo genérico).

Para assinaturas genéricas, é mais complicado por causa da inferência do argumento de tipo. E se você tivesse o seguinte:

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

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

O que foo retorna? Meu ponto é apenas que as pessoas esperam que as regras genéricas sejam as mesmas para tipos genéricos e assinaturas genéricas, mas se você tornar os tipos genéricos mais expressivos, a inferência de argumento de tipo precisa de uma maneira de lidar com isso. Isso pode ser apenas uma questão de identificar formalmente os casos que são difíceis para inferência de argumento de tipo e exigir que o usuário passe argumentos de tipo explícitos nesses casos.

Em termos de minha opinião, acho que sua opção 1 é melhor. Eu pessoalmente não vejo o uso de tipos de tupla como parâmetros de descanso. Acho que um parâmetro de descanso só deve ser um tipo de array ou um parâmetro de tipo de descanso, porque é suposto ter comprimento variável. Também gosto do conceito de parâmetro de tipo de repouso como uma sequência abstrata de tipos, não associada a algo que já existe no sistema de tipos.

Minha filosofia sobre tuplas é que elas representam um subconjunto de valores de matriz onde o comprimento é conhecido. Esses valores de matriz são entidades de tempo de execução reais. Não gosto da ideia de usá-los como uma espécie de dispositivo de sistema de tipos para representar uma sequência abstrata de tipos (por exemplo, a sequência de parâmetros em uma assinatura). Mas se você tem permissão para espalhar um parâmetro de tipo de repouso em uma tupla, é uma história diferente.

Eu gosto da proposta de tupla porque é mais poderosa e resolve mais casos de uso, também é muito intuitivo que eu possa espalhar uma tupla como um parâmetro de resto porque tuplas são apenas matrizes e ao chamar uma função com um parâmetro de resto eu posso espalhar a matriz. O sistema de tipos corresponderia melhor ao meu entendimento do código.

@JsonFreeman em seu caso, foo retornaria [string, number, number] pois isso seria inferido de ...args , o tipo cb inferido seria (string, number, number) => void e o retorno de chamada passado simplesmente ignoraria o último argumento que é muito comum em TS e JS.

Não gosto da ideia de usá-los como uma espécie de dispositivo de sistema de tipos para representar uma sequência abstrata de tipos

Isso é exatamente o que eles são, JS não sabe sobre tuplas, apenas TS. Para TS, uma tupla é uma sequência de tipos.

Eu também gosto da abordagem baseada em tupla. Especialmente se pudéssemos ter assinaturas de funções compatíveis como esta:

// 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 não podemos expressar com a sintaxe atual, mas poderíamos se tivéssemos # 6229 adotado.
Então, para mim, parece que uma maneira adequada é usar tuplas e unificar tuplas para expressar mais. Sem tuplas mais expressivas, seria difícil ter algo como [...T, ...T] porque T como uma tupla tem um comprimento aberto.

@JsonFreeman para seu exemplo, @Pajn mostrou exatamente como meu entendimento disso - não há nenhum problema visível em inferir esses tipos.

@JsonFreeman é melhor eu usar essa sintaxe

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

Hm, provavelmente pode introduzir alguma ambigüidade:

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]

Eu poderia entender a ideia de espalhar um parâmetro de tipo de repouso em uma tupla. Mas não tenho certeza se quero que um parâmetro de tipo resto seja interpretado implicitamente como uma tupla. O exemplo de @Pajn ainda funcionaria se os parâmetros do tipo rest pudessem ser espalhados em todas as posições de sequência de tipo (tuplas, listas de parâmetros, argumentos de tipo).

@Igorbek Você está certo sobre a ambigüidade em seu primeiro exemplo. Seu terceiro exemplo também é problemático. Dada uma sequência como number, string , existem 2 instâncias possíveis da assinatura. Nomeadamente (arg1: number, arg2: string) => [number, string] , bem como (arg1: [number, string]) => [number, string] (adotando a interpretação implícita da tupla para fins de exemplo).

A outra coisa estranha sobre a interpretação implícita da tupla é esta: digamos que você tenha um parâmetro de tipo resto T sendo instanciado para number, string . Agora digamos que você os passe como argumentos de tipo, Foo<T> . Isso deve ser interpretado como Foo<[number, string]> enquanto Foo<...T> é Foo<number, string> ? Há um argumento para isso, pois seria estender o operador de propagação ao sistema de tipos. Mas ainda prefiro que a versão da tupla seja representada como Foo<[...T]>

Pode me chamar de louco, mas sinto algumas falhas fundamentais com a ideia de usar
tuplas. O que acontece se você tentar espalhar um tipo de tupla em muitos
parâmetros? Assim?

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

Além disso, o que acontece se os parâmetros de tipo forem do tipo errado ou usados ​​em
lugares incomuns e 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)

O primeiro exemplo é diretamente relevante para a função # apply (e pode ser um
erro), e o segundo é um erro não óbvio que não será compilado,
e não trivial para detectar com o Intellisense.

No domingo, 28 de fevereiro de 2016, 03:04 Jason Freeman [email protected] escreveu:

A outra coisa estranha sobre a interpretação implícita da tupla é esta: diga
você tem um parâmetro de tipo de repouso T sendo instanciado em número, string.
Agora digamos que você passe esses como argumentos de tipo, Foo. Isso é para ser
interpretado como Foo <[número, string]> enquanto Foo <... T> é Foo string>?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189817561
.

@JsonFreeman

Seu terceiro exemplo também é problemático. Dada uma sequência como number, string , existem 2 instâncias possíveis da assinatura. Nomeadamente (arg1: number, arg2: string) => [number, string] , bem como (arg1: [number, string]) => [number, string] (adotando a interpretação implícita da tupla para fins de exemplo).

Do meu terceiro exemplo fica claro que só pode ser interpretado 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]>;

A outra coisa estranha sobre a interpretação implícita da tupla é esta: digamos que você tenha um parâmetro de tipo de repouso T sendo instanciado para number, string .

T não pode instanciar number, string porque é uma tupla verdadeira. Deve ser [number, string] .

Agora digamos que você os passe como argumentos de tipo, Foo<T> . Isso deve ser interpretado como Foo<[number, string]> enquanto Foo<...T> é Foo<number, string> ?

Verdade. No entanto, ter <...T> parece redundante para os casos de uso em particular que estamos discutindo (capturar tipos posicionados para argumentos restantes). No entanto, digamos que temos.

Há um argumento para isso, pois seria estender o operador de propagação ao sistema de tipos. Mas ainda prefiro que a versão da tupla seja representada como Foo<[...T]>

Existem dois casos em que podemos usar essa sintaxe:

// 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]

Portanto, para uso, nada mais é do que o tipo de operador como | , & ou [] . Mas na declaração essa sintaxe pode ser interpretada como T extends any[] ou qualquer tipo de base para todas as tuplas, para indicar que deve ser um tipo de tupla.

@isiahmeadows

O que acontece se você tentar espalhar um tipo de tupla em muitos
parâmetros? Assim?

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

Além disso, o que acontece se os parâmetros de tipo forem do tipo errado ou usados ​​em
lugares incomuns e 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

Ainda não vejo a vantagem de representar essas coisas como tuplas. Além disso, acho que eles devem ser declarados como <...T> e não <T> . Como eu disse antes, não vejo os tipos de tupla como um dispositivo apropriado para usar em sequências de tipo de comprimento arbitrário no sistema de tipos. Ainda não estou convencido de que isso seja necessário para a expressividade que as pessoas desejam.

Eu concordaria que poderia ser mais expressivo, mas ter o operador 'spread' na posição dos parâmetros de tipo nos limitará a capturar os argumentos restantes apenas uma vez, da mesma forma que não podemos ter os parâmetros restantes duas vezes. Então, dados <...T> e <A, B, C> , T irão pegá-los como [A, B, C] . E não poderíamos expressar <...T, ...U> porque seria ambíguo - [A, B, C], [] ou [A, B], [C] ou ... etc.

Digamos que eu queira expressar uma função com o seguinte comportamento:

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, agora vejo como você está pensando nisso. Parece que o que você está propondo é, na verdade, um recurso diferente do que eu pensava. Em vez de adicionar uma nova construção para capturar uma sequência de parâmetros de tipo, você apenas deseja que os tipos de tupla sejam propagáveis ​​porque já representam uma sequência de tipos. Dessa forma, é possível passar várias tuplas de vários comprimentos de forma mais transparente.

Em javascript, é mais parecido com function foo([...rest]) { } vez de function foo(...rest) { } .

Isso faz mais sentido para mim agora, obrigado por explicar. Acho que é uma abordagem razoável.

@JsonFreeman Exatamente!

@JsonFreeman Pergunta: por que [1, 2] deveria satisfazer [number] ? Isso me parece muito estranho. Isso realmente funcionaria seria muito surpreendente. Não é de todo tipo seguro.

Não que eu tenha algo contra o uso de tuplas para tipos variáveis ​​(sou neutro, para ser honesto).

@isiahmeadows de que forma [1, 2] não pode ser substituída por [number] ? É definitivamente um subtipo. É o mesmo que { x: 1, y: 2 } é um { x: number } válido

OK. Vou conceder parcialmente, mas leve em consideração Function.prototype.apply, que aceita uma tupla de argumentos.

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

Se o chamador lançar um TypeError em muitos argumentos, passar muitos resultará em um erro de tempo de execução, não um erro de compilação como deveria.

Não é muito raro para qualquer função JS lançar TypeError quando passados ​​muitos argumentos? Quais são alguns exemplos?

@isiahmeadows como um exemplo abstrato, entendi que o erro com o qual você está preocupado é:

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.

Isso é correto?

@sandersn , acho que TypeError em muitos argumentos é algo que viola o espírito do JS, já que normalmente passamos a função com argumentos menos formais do que os reais que serão passados ​​para esta função. Simplesmente não os usamos. Por exemplo Array.prototype.forEach

E quanto ao currying de funções? Isso é provavelmente muito mais comum, com Ramda
e lodash / fp.

Na segunda-feira, 29 de fevereiro de 2016, 13:45, Anatoly Ressin [email protected] escreveu:

@sandersn https://github.com/sandersn , acho que TypeError também
muitos argumentos é algo que viola o espírito do JS, pois nós
geralmente passam a função com argumentos menos formais do que os reais que irão
ser passado para esta função. Simplesmente não os usamos. Por exemplo
Array.prototype.forEach

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190327066
.

@isiahmeadows Eu diria que currying baseado em arguments.length é muito instável e sujeito a erros de execução. O verdadeiro currying é à prova de argumentos extras:

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

Quando passo minha função com assinatura fixa como retorno de chamada para algum lugar, penso nisso da seguinte maneira: 'minha função espera _pelo menos_ esses argumentos'

E sobre coisas 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))

Isso é muito comum na programação funcional. E omitindo o último
argumento é bastante comum.

Na segunda-feira, 29 de fevereiro de 2016, 13:52, Anatoly Ressin [email protected] escreveu:

@isiahmeadows https://github.com/isiahmeadows eu diria que currying
com base em aruments.length é muito instável e sujeito a erros de tempo de execução.
O verdadeiro currying é à prova de argumentos extras:

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

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Se você quiser passar isso como um retorno de chamada para, digamos, map (trabalhando em uma lista
de listas), você provavelmente vai querer curry-lo.

Na segunda-feira, 29 de fevereiro de 2016, 13:59, Isiah Meadows [email protected] escreveu:

E sobre coisas 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))

Isso é muito comum na programação funcional. E omitindo o último
argumento é bastante comum.

Na segunda-feira, 29 de fevereiro de 2016, 13:52 Anatoly Ressin [email protected]
escreveu:

@isiahmeadows https://github.com/isiahmeadows eu diria que currying
com base em aruments.length é muito instável e sujeito a erros de tempo de execução.
O verdadeiro currying é à prova de argumentos extras:

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

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Acho que é principalmente sobre isso:

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 isso que propus # 6229

A questão de se [1, 2] satisfaz [number] é válida para perguntar e debater. Mas o que isso tem a ver com o recurso de tuplas expansíveis?

É se uma aplicação variável de tuplas deve ignorar argumentos extras
ou não. Essa função sobrecarregada deve elaborar mais sobre minha preocupação.

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)

Na segunda-feira, 29 de fevereiro de 2016, 18:47 Jason Freeman [email protected] escreveu:

A questão de saber se [1, 2] satisfaz [número] é válida
e debate. Mas o que isso tem a ver com o recurso de tuplas expansíveis?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

E é por isso que, por razões práticas, eu preferiria rest como parâmetro
tipos variadic.

Na segunda-feira, 29 de fevereiro de 2016, 19h, Isiah Meadows [email protected] escreveu:

É se uma aplicação variável de tuplas deve ignorar
argumentos ou não. Esta função sobrecarregada deve elaborar mais do meu
interesse.

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)

Na segunda-feira, 29 de fevereiro de 2016, 18:47 Jason Freeman [email protected]
escreveu:

A questão de saber se [1, 2] satisfaz [número] é válida
e debate. Mas o que isso tem a ver com o recurso de tuplas expansíveis?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

@JsonFreeman isso A , B e T = [A] , então [...T, B] construirá [A, B] " (o que é implicitamente proposto), então ele não seria alinhado com o operador de propagação de matriz / tupla. Dados var a: [A] e var b: B , o tipo de expressão [...a, b] não pode ser provado como sendo do tipo [A, B] . De acordo com as regras atuais de tuplas, pode-se provar que é do tipo [A, A|B] .
Isso faz sentido para você? Ou posso criar uma tabela de comparação para destacar essa incompatibilidade.

@Igorbek Eu entendo o que você está dizendo. Em última análise, decorre do fato de que o compilador tem um conhecimento perfeito dos tipos com os quais está lidando, mas não um conhecimento perfeito dos valores. Em particular, em seu exemplo, o valor a tem comprimento desconhecido, enquanto o tipo [A] tem comprimento conhecido. Esse foi um dos motivos pelos quais fiquei inicialmente desconfortável em usar tipos de tupla para essa finalidade. Mas não tenho certeza se é um problema sério.

@isiahmeadows Vejo o que você está perguntando, mas por que o problema é mais claro com os parâmetros de tipo de repouso? Se você tiver mais argumentos do que argumentos de tipo, a mesma pergunta pode ser feita.

A solução de tipo seguro seria mais consistente com o resto do
idioma se ele imitou a sintaxe do argumento.

Meu ponto é se você estiver efetivamente espalhando um parâmetro de descanso, você obtém
exatamente os tipos de argumento e nada mais. Funções curry têm um retorno
tipo depende do tipo de argumento. Então, se você aplicar muitos argumentos
para aplicar parcialmente uma função curried, você obterá uma função completamente diferente
modelo. O tratamento de tipos de descanso como tuplas levaria a erros de tempo de execução,
que nunca são bons.

Na terça-feira, 1º de março de 2016, 06:07 Jason Freeman [email protected] escreveu:

@Igorbek https://github.com/Igorbek Eu entendo o que você está dizendo.
Em última análise, decorre do fato de que o compilador tem conhecimento perfeito
dos tipos com os quais está lidando, mas não um conhecimento perfeito dos valores. No
em particular, em seu exemplo, o valor a tem comprimento desconhecido, enquanto o
tipo [A] tem comprimento conhecido. Esta foi uma das razões pelas quais eu estava inicialmente
desconfortável em usar tipos de tupla para essa finalidade. Mas eu não tenho certeza
é um problema sério.

@isiahmeadows https://github.com/isiahmeadows Vejo o que você está perguntando
sobre, mas por que o problema é mais claro com os parâmetros de tipo de repouso?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190667281
.

@isiahmeadows você pode dar um exemplo de código para o problema do currying?

Eu ainda acho que mesmo se você usasse parâmetros de tipo rest (que eu sou totalmente a favor), você teria que decidir explicitamente não permitir argumentos em excesso, mas eu concordo com @isiahmeadows que você provavelmente deveria.

@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

Vou adicionar minha variação também. Vamos ver o exemplo de curry variadic da proposta:

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

Então, comecei a usá-lo:

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 Quer sejam representados como parâmetros de tipo de repouso ou como tipos de tupla, parece que você se opõe à capacidade de espalhá-los em uma posição de tupla.

@Igorbek Acho que seu exemplo é semelhante no sentido de que o problema não é como as sequências de tipo variável são representadas. É a capacidade de espalhá-los em tuplas que leva aos problemas.

@JsonFreeman Tenho mais objeções a este comportamento:

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)

Isso esclarece?

@isiahmeadows deve funcionar na verdade

@JsonFreeman
Eu sinto que não deveria. Essa é minha maior objeção. Eu sinto que é potencialmente perigoso se for.

Pergunta: qual deve ser o 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()])

Na verdade, isso é muito importante.

Esse último exemplo definitivamente parece que não deveria funcionar. Essencialmente, você precisa de um mecanismo para alinhar sequências de tipos se function.bind realmente funcionar corretamente. Você precisaria de algo semelhante à unificação, em que os tipos de argumentos a serem vinculados correspondem aos argumentos da função original e, em seguida, o restante está no tipo de retorno.

Dito isso, não parece que nada no que foi proposto ou discutido possa lidar com isso (independentemente de argumentos extras de tuplas serem permitidos), embora seja possível que eu tenha esquecido alguma coisa.

Acho que o maior problema é que algum tipo de correspondência de padrão de tupla, onde cada tipo de parâmetro é correspondido com os tipos de propagação, precisa ser feito com os parâmetros de tipo (como os argumentos de LiveScript / CoffeeScript) para corrigir esse problema. Provavelmente é impossível de outra forma. E quanto ao quão complicado é, boa sorte para implementá-lo. :sorriso:

@JsonFreeman

Ou, para ser mais preciso, exigirá verificação de tipo não estrita (no sentido de ansioso versus preguiçoso) para funcionar. Eu também acho que é provavelmente uma extensão mais útil do que apenas tipos variáveis, de qualquer forma, já que ela praticamente abre a porta para muitas outras coisas mais úteis, como 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>[]

Felizmente, a verificação de tipo não estrita deve ser uma mudança puramente ininterrupta em que apenas o código que anteriormente falhou na verificação agora funciona.

Essa é provavelmente a maior coisa que bloqueia a digitação adequada de Function.prototype.bind , além do fato de que exigirá uma assinatura de tipo muito complexa.

Essa é uma conexão interessante. Não estou convencido de que sejam parentes. O problema de tipos recursivos é uma consequência da política de cache para genéricos e da representação de apelidos de tipo no compilador. Todas as informações estão lá, é apenas o design do compilador que atrapalha.

Para a correspondência de padrão de tupla, você nem sempre pode saber quantos argumentos estão sendo comparados com a tupla. Se você espalhar um array nos argumentos de bind , você não sabe quantos são restantes no retorno de chamada resultante.

@JsonFreeman Dito isso, você acha que, como etapa de adoção, a proposta do operador de propagação de argumento # 6229 precisa ser considerada primeiro?

@JsonFreeman

E a verificação não estrita dos tipos permitiria preguiça suficiente para tornar mais fácil corrigir o problema com Function.prototype.bind . Com essa preguiça, você poderia realizar esse tipo com o seguinte (o que exigirá uma sintaxe de tupla para sequenciá-los, a menos que vários parâmetros restantes estejam corretos em uma declaração 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 que isso exigiria uma verificação de tipo não estrita para inferir? Você tem que deduzir o tipo de repouso passo a passo para verificar a função. Dado o seguinte, é assim que deveria 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

É assim que funciona a verificação não estrita de tipo. Ele infere tipos conforme necessário, em vez do instante em que os vê. Você pode (e deve) combinar as duas passagens, para que os tipos errados falhem. Exemplo:

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

Nesse caso, o parâmetro esperado deve ser o primeiro inferido nessa rodada, fazendo uma iteração em profundidade. Nesse caso, o primeiro inferido nessa pesquisa foi uma string, e os símbolos não podem ser atribuídos a strings, então isso falha com isso.


E por causa disso e tentando digitar Function.prototype.apply , minha opinião sobre o uso de tuplas para aplicar os tipos de repouso mudou.

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

Algumas outras notas:

  1. Deve haver uma maneira de espalhar matrizes e tuplas como parâmetros de tipo de resto.

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

  1. Deve haver dois tipos separados para construtores e chamáveis, com Funções sendo a união dos dois. Os objetos que podem ser chamados devem implementar a interface que pode ser chamada, os construtores da classe devem implementar a interface que pode ser construída e as funções ES5 devem implementar a união dos dois.
  2. Function.prototype.bind e seus amigos devem verificar todas as sobrecargas para a função. Se houver vários que funcionam, ele deve retornar uma união de todos eles.

Esses parâmetros de tipo em seu exemplo não são realmente os parâmetros de tipo da assinatura de ligação. Eles pertencem ao tipo Function. Mas sim, a ideia é que se você pudesse usar dois parâmetros de repouso, ou espalhar dois parâmetros de tipo de repouso em uma tupla, você seria capaz de escrever isso.

Para que a assinatura de bind seja flexível o suficiente, o limite entre ...X e ...Y precisa ser decidido por chamada. Precisaria ser inferido. No entanto, seria um problema se uma assinatura usasse ...X isoladamente. Neste caso, o limite não terá sido decidido. Por exemplo:

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

E as sobrecargas são um grande problema para o tipo de função. Não acho que você queira usar o tipo de união elemento a elemento em cada argumento, porque isso permitiria a mistura e a correspondência de parâmetros entre sobrecargas. É isso que você quis dizer?

@JsonFreeman

_TL; DR: Pule para a quebra de linha horizontal. Tenho uma ideia nova e mais prática._

  1. Sim, estou ciente de que eles realmente pertencem ao próprio Function .
  2. Esse tipo de problema é o motivo pelo qual eu disse que a correspondência de tipo não estrita (no sentido de Haskell) é necessária. Você não pode resolver avidamente o tipo normalmente, porque isso exigiria uma pesquisa iterativa e preguiçosa para fazer. É possível determinar algoritmicamente, mas você teria que controlar as coisas que normalmente não precisariam ser rastreadas em C ++.
  3. Se os dois argumentos estiverem isolados (como no seu exemplo), o compilador deve reclamar. E essa situação pode ser detectada com uma análise de dependência de nível de tipo de cada argumento variadic na interface / qualquer coisa. Também não é trivial, mas pode ser verificado ao ler a própria declaração de tipo (na verdade, logo depois).

Embora eu também esteja pensando que pode ser um pouco mais viável definir esses tipos de situações apenas com o método em questão. Também será muito mais fácil e rápido detectar os tipos de problemas potenciais aos quais você aludiu.

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
}

Existe um outro problema potencial que será muito mais difícil de resolver (e por que eu acho que deveria ser restrito a 2, não _n _ divisões):

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(), {}, []]
)

_Desculpe se estou me aprofundando muito na teoria do CS aqui ..._

Sim, acho que é a ideia certa. Não é bonito, mas não consigo pensar em nenhuma outra maneira de digitar corretamente bind , sabendo os argumentos de tipo de Function . A última coisa é que um limite deve ser inferido. E eu concordo que deve ser limitado a 2 intervalos para que você tenha que inferir 1 limite, em vez de algum número arbitrário de limites, que podem explodir combinatoriamente.

Provavelmente, há mais questões nas quais não pensamos.

@JsonFreeman Outro problema são coisas como curry . Eu ainda estou para pensar em algo que possa digitar isso corretamente. E vai demorar um pouco antes que eu possa. Eu teria que fazer algum tipo de hackeamento do tipo Haskell sério para chegar a esse processo.

Pensando em como pensar tipo de proposta poderia funcionar com algumas funções do 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>;
}

Temos uma solução para all<...T>(promises: [...PromiseLike<T>]): Promise<...T>; acima?

@DerFlatulator

Veja meu grande comentário no PromiseConstructor. Eu também corrigi a interface do seu Promise para ficar um pouco mais perto da minha proposta.

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>;
}

Se [...Foo<T>] expande para [Foo<T1>, Foo<T2>, /*... Foo<TN>*/] , então [...Foo<T,U>] um erro de sintaxe ou uma expansão combinatória?

@DerFlatulator

  1. Se exatamente um de T ou U for um parâmetro de descanso, ele se expande normalmente. Supondo que T seja um parâmetro de descanso, seria [Foo<T1, U>, Foo<T2, U>, /*... Foo<TN, U>*/] .
  2. Se ambos são parâmetros de repouso, e podem ter seu comprimento inferido corretamente, deve ser uma expansão combinatória (bem ... comprimento de T vezes comprimento de U).
  3. Se nenhum dos parâmetros for rest, é um erro de sintaxe.

Observe que eu me oponho fortemente a mais de 2 parâmetros de descanso por razões práticas, e que os parâmetros de descanso, se eles precisarem ser divididos, devem ser divididos apenas por método. Algo assim:

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
}

_ (Se alguém puder sugerir uma sintaxe melhor, sou todo ouvidos. Não gosto disso, mas não consigo inventar nada que não entre em conflito visual.) _

@isiahmeadows

Com 2., em que ordem seria a expansão?

[
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>
]

Ou inversamente:

[
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>
]

Essa ambigüidade não causaria confusão? Talvez seja sensato limitar-se a uma dimensão.


Apenas uma sugestão alternativa para a sintaxe de divisão:

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

Eu esperaria o segundo. E eu duvido que causaria muita confusão, já que contanto que seja consistente, as pessoas se acostumariam rapidamente. É também um caso extremo incomum que só seria realmente encontrado na prática por pessoas que sabem o que estão fazendo, ou pessoas que deveriam questionar a necessidade em primeiro lugar.

Também estou observando enquanto você expande o primeiro, depois o segundo para cada parte do primeiro. Como este pseudocódigo:

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

Repetindo algumas das ideias acima ...

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

Aqui, [...TBound, ...TUnbound] = [...TArgs] é válido porque o comprimento de ...TBound é conhecido pelo comprimento de args . Também permite alterar o tipo de TThis .

Um problema com essa abordagem é que você só pode vincular this uma vez, por exemplo:

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>

O tipo inferido de y está incorreto, deveria ser Function<number, IBar> . Não tenho certeza se isso é uma preocupação ou não, mas resolvê-lo exigiria a introdução de lógica na sintaxe <T> .

Opção 1

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

opção 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>;
}

No entanto, isso provavelmente estaria fora do escopo desta proposta.

Não acho que devemos permitir esse tipo de expansão usando o operador de propagação de tipo. Eu penso no operador de propagação como um "removedor de colchetes" que está absolutamente alinhado com o operador de propagação de matriz e o operador de propagação de objeto / propriedades (proposta de estágio 2). Basta comparar:

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 };

Você está sugerindo estendê-lo para a construção de um novo conjunto de tipos:

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

É uma operação totalmente diferente. Se você quiser, ele pode ser modelado por construções de ordem superior, como:

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

@Igorbek Que tal usar um operador para determinar o que será expandido?

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

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

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

Algumas alternativas de sintaxe:

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

Eu concordo com @Igorbek aqui. Pelo menos neste estágio, mapear sequências de tipos não parece uma prioridade, visto que ainda estamos tentando resolver o problema mais básico dos parâmetros de tipo variadic.

Eu não tenho muito problema em proibir isso (pelo menos inicialmente), já que o comportamento para isso é muito pouco intuitivo, e duas pessoas diferentes podem esperar duas coisas muito diferentes, até. Eu concordo com @Igorbek pelo menos por agora, já que o TypeScript primeiro precisa ter um modelo de tipo de ordem superior (isso é map ping em um tipo em certo sentido). E os tipos de pedido superior não são exatamente algo que você possa simplesmente adicionar.

Então, definitivamente: +1: por proibir isso, provavelmente por um bom tempo. Embora seja bom de ter, é complicado como o inferno de implementar e seria um hack completo de se fazer, uma vez que o TypeScript não usa um sistema de tipos seguro e funcional.

Chegou um pouco atrasado, mas também concordo com @Igorbek . Reiterando meu comentário feito em # 1336 e pegando emprestado as idéias do empacotamento de parâmetros C ++ tendo os operadores 'empacotar' e 'descompactar' claros.

Empacotar tipos em uma tupla parece consistente com o uso do Typecript do operador spread:

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];

O que torna <...T_values> = [T1, T2, T3, etc...] muito mais fácil de raciocinar.

Enquanto C ++ usa o operador spread para compactar e reticências para desempacotar, usar spread para ambos é mais consistente com o Typescript.

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 qual seria o objetivo de tudo isso sem mapear?

Também conforme levantado em # 1336, que tal um Array.flatten variável?

@jameskeane Essa primeira metade foi a ideia inicial, mas não cobre o caso de um parâmetro de resto do meio (que algumas APIs têm):

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

Também não cobre Function.prototype.apply vs Function.prototype.call muito bem.

Quanto ao # 1336, pode ser implementado de forma semelhante por meio deste:

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

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

Eu me atualizei e percebi que estava ingenuamente presumindo que os tipos de tupla eram de comprimento estrito; qual imo é o mais intuitivo. Então, supondo que recebamos tipos de tupla de comprimento estrito (# 6229), quais são os problemas?

@isiahmeadows Em seu exemplo acima do caso do parâmetro de descanso intermediário, não é resolvido por ter tuplas de comprimento estrito? Estou lendo ...rest: [...T, Baz] igual a spread desempacotando arr = [...other, 123] . Este é o mesmo problema que você levantou com curry , certo?

Quanto a apply e call , eles não estão cobertos por tipos de interseção? (Não que eu realmente veja o valor em ter os tipos na interface Function qualquer maneira).

// 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

A proposta variadic atual assume # 6229 realmente acaba sendo aceita (ou seja, tuplas são estritas por padrão).

Quanto a func.apply , func.bind , func.call e _.curry , o único problema é com func.bind , _.curry , e amigos, ou mais geralmente qualquer coisa usando aplicativo parcial. Você também precisa ser capaz de escolher qual parâmetro restante separar, e isso só pode ser feito por método.

call e apply são bastante diretos:

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 seria mais difícil. Os parâmetros de divisão teriam que ser combinados conforme necessário, ao contrário de ansiosamente, como é o caso agora, até que a primeira metade da divisão seja totalmente descompactada. Isso deve ser implementado como sintaxe, para que o compilador possa diferenciá-lo e discernir corretamente o tipo sem avaliar 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 seria extremamente difícil, pois deve-se saber que f(1, 2, 3) === f(1, 2)(3) === f(1)(2, 3) === f(1)(2)(3) . Não só deve haver a capacidade de dividir um parâmetro restante em dois, como em bind , mas também a capacidade de realizar correspondência de padrões muito 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>;

Não acredito que as adições de curry o tornariam Turing-completo, mas ficaria perto. Acho que o principal impedimento seria a capacidade de combinar especializações de um tipo específico (que C ++, Scala e Haskell, três linguagens com sistemas de tipo Turing-completos, possuem).

@sandersn Não consegui ver um exemplo acima, mas posso perguntar sobre as restrições nos parâmetros variáveis?

Considere o seguinte exemplo:

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

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

_Incidentalmente, consulte https://github.com/Microsoft/TypeScript/issues/7848 para uma discussão sobre a possível eliminação do requisito de que X precisa ser listado_

Agora, há potencialmente alguma ambigüidade aqui quanto a se a restrição é:

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

Neste exemplo, estou assumindo 2.

Esses tipos de restrições (1 e / ou 2) serão possíveis?

@myitcv 2 provavelmente seria o melhor caminho a seguir, mas faria sentido apenas reutilizar a lógica existente para verificar a restrição.

Bem ... Acabei de perceber uma coisa: como seria as matrizes contendo tipos variadic? Ou mais especificamente, que tipo está arg abaixo?

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

Eu acho que você está perguntando qual é o tipo de elemento de args . Para tuplas, acredito que esse geralmente seja o tipo de união dos elementos. Não consigo pensar em uma maneira melhor de digitar isso.

@sandersn você pode comentar qual é o status desse recurso? Acho que tem havido muita discussão, mas não parece que haja um plano definido para o recurso, correto?

@JsonFreeman Eu estava perguntando especificamente o que era arg . Na minha opinião, deveria ser any para o meu exemplo original e Item<T> com abaixo (com o T limitado por F):

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

Isso ocorre para que os tipos possam ser resolvidos localmente. Você não conhece os tipos de antemão, e isso irá acelerar a compilação tremendamente, uma vez que você não precisa computar os tipos dentro da função para cada chamada a ela. Observe que se você só precisa do tipo de um único argumento, typeof arg será suficiente e provavelmente será mais curto.

Oh, desculpe, para o exemplo original, eu quis dizer que o tipo deveria ser T . Na verdade, para o seu segundo exemplo, também acho que deveria ser T.

Eu quis dizer Item<any> no segundo ... Desculpe.

Quando eu disse que deveria ser T, estava assumindo que T é um tipo, mas acho que o ponto principal desse recurso é que T não é um tipo (eu acho). Então, sim, acho que deveria ser any e Item<any> em seus exemplos.

Mas, de forma mais ampla, estou curioso com o quão ativamente a equipe está considerando esse recurso para depois da 2.0. Eu não tenho uma opinião forte, apenas me pergunto.

A razão pela qual não acho que deva ser necessariamente T é porque você não sabe o que é T . A menos, é claro, que você queira que a variável T represente um único tipo da lista de tipo variável ou, quando espalhada, a própria lista, ou seja, T é o subtipo de todos os argumentos passados ​​para o argumento ...T , e [...T] ser atribuído a T[] .

Ou, para esclarecer o que quero dizer em todo o jargão pouco claro, aqui está o que quero dizer em termos 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
    }
}

Isso provavelmente faria mais sentido e seria muito mais flexível.

Ainda está em nossa lista de coisas legais, mas é principalmente de interesse para autores de bibliotecas e tem uma solução alternativa decente - _n_ sobrecargas - portanto, não estou trabalhando ativamente nisso. Se eu tivesse que adivinhar, diria que 2.1 é possível, mas improvável.

Se / quando nos comprometermos a apoiar adequadamente o repouso / propagação do objeto (# 2103), então os tipos variáveis ​​podem ser próximos o suficiente para tipos de propagação para justificar fazê-los todos de uma vez. (Os tipos de propagação são uma variante dos tipos de objeto que se parecem com { ...T, x: number, ...U, y: string, ...V } .)

Só quero mencionar que a solução alternativa n overloads não funciona para classes ou interfaces, que é meu interesse particular neste recurso.

@sandersn Uma solicitação pull para "_n_ sobrecargas" seria convidada para bind , apply e call em funções, usando this digitação? Acho que seria um compromisso temporário aceitável para muitos e poderia detectar alguns bugs no processo para alguns projetos.

@isiahmeadows

A razão pela qual eu não acho que deveria ser necessariamente T é porque você não sabe o que é.

Pareceu-me que havia concordância de que T é um tipo de tupla dos tipos variáveis. Em seu exemplo original, o tipo de arg seria o mesmo que o tipo de elemento de tupla (como @JsonFreeman observou, "o tipo de união dos elementos"): Por enquanto, imagine que o

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]

Separado desta proposta, acho que deveria haver uma maneira de representar o 'tipo de elemento' de uma tupla. Isso poderia ser outro uso para spread, ou seja, acima de ...T :: number|string|boolean ; que espalhar o tipo de tupla resulta em seu 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;
  }
}

Com isso em mente, seus outros exemplos:

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

  }
}

Sem perder de vista meu objetivo original, eu queria um Promise.all fortemente 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 Agora que outros recursos solicitados estão começando a depender disso, a prioridade poderia ser aumentada? bind , call etc. a digitação depende disso, e a sintaxe do ES bind se / quando sair depende disso, então agora há mais coisas acontecendo nisso do que autores de biblioteca peculiares importunando você o tempo todo . :)

Não que isso seja particularmente construtivo para adicionar, mas eu ficaria muuuito feliz se esses dois recursos chegassem ao 2.1. Eu conheço pelo menos uma biblioteca (RxJS), onde não só a própria base de código seria melhorada por esses recursos, mas o código de consumo também seria significativamente menos complicado e sujeito a bugs (cada terceira pessoa que começa com Angular 2 é mordida por importações ausentes para operadores que são corrigidos no protótipo observável). Realmente seria um recurso inovador para pessoas que desejam escrever código funcional sustentável.

Isso poderia ser usado para fornecer uma definição de tipo completa para _.extend , cujo tipo de retorno é a interseção de todos os seus parâmetros?

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

Não como está. É necessária uma extensão da proposta que forneça detalhes sobre um novo operador para tipos variáveis ​​- provavelmente chamado &. @kitsonk propôs este operador anteriormente, neste comentário .
No momento, esse recurso está abaixo de algumas outras coisas mais importantes imediatamente, então não vejo essa proposta há algum tempo.

Embora não forneça tipos variados completos, o # 10727 é parte da solução (e é provável que resolva os desafios que nós (@dojo) temos).

Bom de se ouvir! Embora ainda não sejam tipos realmente variáveis. :( Por exemplo, esta semana, quando tentei digitar Object.assign , cheguei até aqui:

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 };
}

Observe que a sintaxe dos "seis pontos" é a propagação do objeto de uma variável do tipo tupla, que realmente não discutimos acima.

@sandersn

Com Object.assign em particular, ele poderia ser digitado dessa forma e, tecnicamente, capturar um subconjunto (embora um pouco irremediavelmente fraco), já que altera seu alvo (você teria que ter um ponto de referência para isso):

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

A falha com isso é que ele muda seu alvo, mudando seu tipo estrutural no local.

@isiahmeadows então a inferência fixaria T para ser o tipo de target sem os tipos de contabilidade sources . Você pode tentar agora com a versão não variável:

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

Como já mencionado, assign usa _um tipo de propagação_ # 10727 e pode ser definido desta forma:

// 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: ainda insisto na sintaxe baseada em tupla [...T] que tem muito mais sentido para mim._

@sandersn BTW, há alguma atualização sobre quando os tipos
E, em relação à sintaxe, vocês ainda aceitam feedback sobre a sintaxe ou estão todos de acordo com isso?

A sintaxe e a semântica de baixo nível ainda não têm um consenso claro.

Na terça, 13 de dezembro de 2016, 13:26 Igor Oleinikov [email protected] escreveu:

@sandersn https://github.com/sandersn BTW, qualquer atualização sobre quando
tipos variados vão ser desembarcados? Há alguma chance de vê-lo no 2.2?
E, em relação à sintaxe, você ainda aceita comentários sobre a sintaxe ou
vocês estão todos de acordo com isso?

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-266819647 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AERrBIa5fE8PSk-33w3ToFqHD9MCFoRWks5rHuM5gaJpZM4GYYfH
.

Alguma ideia de qual é a situação desse problema?

então quais são as opções em que você está pensando? isso está na agenda com a equipe? é a única parte fraca do sistema de tipos que encontro repetidamente. eu tenho dois casos de uso. um simples e um complexo, mas mais geral.

simples seria adicionar um supertipo Tuple extends any[] que só pode ser subtipado por tipos de tupla. Como os spreads precisam ser subtipos de any[] , isso funcionaria:

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

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

no momento, ...args: T[] só é permitido ao final das assinaturas.

o caso de uso complexo precisaria de ...args: Tuple para ser válido em qualquer lugar dentro de uma assinatura (o que não é problema, pois as tuplas têm comprimento fixo):

/**
 * 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)

Sim, comecei com o TypeScript ontem e isso já tornou impossível digitar automaticamente minhas funções (ainda posso fazer manualmente, é claro) por causa de um único decorador que estou usando para envolver minhas funções (que é a primeira coisa Tentei começar com!):

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

Efetivamente, tudo o que o decorador faz é permitir que uma função seja chamada como um método, para que possam ser anexadas como um método e funcionem de forma idêntica, como um exemplo básico, aqui está um exemplo ruim que corrige o protótipo Array com uma versão 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])

Agora, esperançosamente, é óbvio que o tipo de flatMap (não _flatMap ) é:

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

No entanto, não tenho como adicionar types ao portátil porque não posso extrair o tipo de parâmetro de _flatMap para usar na definição de tipo da função decorada, imagino que com esta proposta eu poderia escreva 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
}

Eu só queria compartilhar isso, pois mostra minhas experiências iniciais com o TypeScript e talvez mostre outro caso de por que os genéricos variados são importantes.

@sandersn :

tem uma solução decente - n sobrecargas

Embora tecnicamente não seja incorreto, sinto que isso não reflete totalmente a realidade aqui. Sim, tecnicamente a falta disso não poderia impedir que sobrecargas digitassem qualquer função mencionada neste tópico; e, ainda assim, esse pequeno inconveniente significa que nenhuma dessas soluções baseadas em sobrecarga chegou a lib.d.ts até agora.

E, de fato, muitos neste tópico se sentiram desesperados o suficiente para abordar suas respectivas funções para propor ainda mais sintaxe que não fazia parte originalmente desta proposta, incluindo ...... , bem como ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> e <PromiseLike<T*>> .

Acho que isso mostra que estamos todos tentando resolver os problemas aqui, compartilhamos um senso geral de que precisamos de uma sintaxe mais poderosa como esta e esperamos que qualquer caminho que escolhermos aqui nos ajude a resolvê-los.

Nota lateral: para R.path Ramda, geramos uma digitação de milhares de linhas de sobrecargas, que ainda perdiam o suporte de tupla (as permutações teriam explodido de forma ainda mais difícil), e apenas causaram a compilação em projetos reais para não terminar mais. A iteração recentemente descoberta como uma alternativa aparentemente viável (# 12290).

A propósito, afaik você ainda não comentou a proposta feita por @Artazor e @Igorbek. Quais foram seus pensamentos sobre isso?

Eu gostaria de argumentar com uma implementação básica como essa aqui (mais # 6606) que podemos fazer quase tudo. Vou oferecer algumas soluções aqui para ilustrar isso, mas estou aberto a mais perguntas.

Deixe-me primeiro examinar alguns lugares onde um operador ... poderia ser implementado:

v ... para | definição (captura) | usar (espalhar)
- | - | -
função | type Fn = (...args: any[]) => {} | type Returns = typeof fn(...MyTuple); (# 6606)
matriz | desestruturação de tupla em nível de tipo. pode ser tecnicamente emulado usando índice de acesso + propagação (veja à direita) + recursão. | type Arr = [Head, ...Tail];
objeto | desestruturação de objetos em nível de tipo. não é necessário, apenas use Omit , consulte # 12215. | type Obj = { a: a, ...restObj }; (não necessário, igual a Overwrite , consulte # 12215)
genéricos | definir type Foo<...T> para fazer Foo<1, 2, 3> (captura [1, 2, 3 ] em T ). divertido, mas não sei que caso de uso requer isso. | definir type Bar<A,B,C> para fazer Bar<...[1,2,3]> ( A = 1 etc.). idem, não sei de casos de uso que precisam disso.
sindicatos (bônus) | ? | type Union = "a" | "b"; type MyTuple = ...Union; // ["a", "b"] (pedido não confiável, mas permite a iteração de uniões / objetos por meio de tuplas. De qualquer forma, fora do escopo aqui).

Portanto, há apenas duas instâncias de nível de tipo ... que são imediatamente relevantes; especificamente, os dois usados ​​aqui:

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

No contexto de # 6606, outro se torna relevante: a capacidade de desempacotar um tipo de tupla para a aplicação de função, por exemplo, typeof f(...MyTuple) . Acho que isso é suficiente para resolver os problemas mais difíceis dos quais ouvi falar aqui. Para tentar oferecer algumas soluções aqui:

@jameskeane :

Eu acho que deveria haver uma maneira de representar o 'tipo de elemento' de uma tupla

Se você gostaria de obter a união de seus elementos, consulte meu 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 são apenas versões variáveis ​​de mergeAll de Ramda. Não são necessários seis pontos!

@isiahmeadows :

Alguma ideia de qual é a situação desse problema?
A sintaxe e a semântica de baixo nível ainda não têm um consenso claro.

Se bem entendi, você principalmente se preocupou se a abordagem oferecida por alguns dos outros levaria em conta o enfrentamento de tipificações mais difíceis, como curry e bind você mencionou. Aqui está minha opinião sobre aquele em particular, seguindo sua proposta.
A estratégia é um pouco semelhante, enganando o fato de que é difícil dizer, extrair requisitos de tipo para parâmetros i ~ j de um tipo de função para um tipo de tupla, adiando a verificação de tipo de argumentos para o aplicativo da função.

// 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])`
}

Sim, eu usei vários tipos de auxiliares - apenas tentando nos contentar com o que temos (+ imagine o que poderíamos fazer com apenas um pouco mais). Não sou muito contra ...... , ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> ou <PromiseLike<T*>> . Mas IMO, mesmo apenas ... ajuda a resolver um problema real agora, e eu adoraria ver isso resolvido.

Edit: Resolvi a restrição de argumento para bind .

Provavelmente apenas uma pergunta estúpida. Isso parece muito promissor para poder digitar corretamente as funções de curry.
Mas, para alguns projetos do mundo real, não se pode querer perder muito tempo digitando partes de código orientadas para programação altamente funcional.
Portanto, estou imaginando, uma vez que --strict está habilitado por padrão em _tsconfig.json_, se poderia haver uma maneira de desabilitar a verificação de tipo para uma parte do código (por preguiça ou falta de tempo).
Mas, como eu disse, provavelmente é uma pergunta estúpida ... ^ _ ^

@ yahiko00 tipo off topic, mas use a seção exclude em tsconfig ou diferente tsconfig s em diferentes níveis de projeto.

Eu também gostaria de fazer outra sugestão, poderíamos tê-la para que & e | trabalhem com um único argumento de tupla com esta sintaxe:

<...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;

A sugestão acima de @HyphnKnight seria muito útil para algo que também estou fazendo.

Quero acrescentar um aviso de que esta proposta não está sendo trabalhada ativamente. Mas eu encontrei exatamente o tipo de artigo da "técnica anterior" que queria ler quando comecei a olhar para este problema: http://www.ccs.neu.edu/racket/pubs/esop09-sthf.pdf

Vou deixar isso aqui para referência futura.

Abri alguns PRs experimentando isso:

  • [] # 17884 spreads em tipos de tupla (WIP)
  • [x] # 17898 extrair parâmetros restantes (pronto)
  • [] # 18007 spreads em tipo de chamada (WIP)
const c = 'a' + 'b';

Pode resolver o problema? Tipo de inferência de c é 'ab' não string

Uma pergunta relacionada no StackOverflow: último parâmetro de função explícito no TypeScript

@sandersn Sua proposta cobriria este caso, pelo que posso ver, correto?

Mais de 2 anos desde a proposta inicial, ainda devemos permanecer esperançosos?

Olá!
Estou tentando digitar um gerador que pega um número variável de matrizes e mistura e combina elementos deles para criar uma nova matriz.
Quero usar este gerador em um loop for...of , mas não consigo digitar corretamente os valores.
Código (pode haver erros, pois ainda não o executei, mas é isso que estou tentando fazer):

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;
            }
        }
    }
}

Exemplo de uso:

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

Não consigo descobrir nenhuma maneira de obter a digitação para target e child sem criar uma variável intermediária.

Algo assim seria bom?

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

@Griffork a prática correta, até que esta proposta seja implementada, é criar muitas sobrecargas para as funções
por exemplo, consulte os tipos promessa.all
https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L41 -L113

Acho essa sintaxe muito confusa:

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

isso parece muito mais natural para mim:

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

Em tempo de execução, um parâmetro de descanso é uma matriz, e podemos fazer isso atualmente no TS:

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

Portanto, parece lógico apenas remover a restrição de args sendo uma matriz de T e, em vez de permitir que o compilador TS inferir um tipo de tupla para T , por exemplo

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

Percebi que algumas questões foram levantadas sobre as tuplas e não as entendo totalmente, mas só queria ponderar que, na proposta atual, é difícil entender quando um desenvolvedor precisaria usar ... em uma posição de tipo e as tuplas são muito mais intuitivas.

... ainda faz sentido para mim concatenar dois tipos de tupla, como [...T, ...U] .

@felixfbecker
A proposta para

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

Seria que T é um tipo de tupla criado dinamicamente, então se você passar um string e um int para a função, então T é [string, int] .
Isso é particularmente interessante se você deseja expressar dinamicamente um padrão 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.

No momento, se você deseja escrever uma função que recebe um número variável de argumentos e digitá-los genericamente, você deve escrever uma sobrecarga genérica para cada número de argumentos que a função poderia possivelmente receber. A proposta ...T essencialmente nos permite fazer com que o compilador gere automaticamente as definições de função para nós.

Sua proposta:

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

Força todos os parâmetros a serem tratados como o mesmo tipo, e eles não podem ter uma verificação de tipo mais específica. Por exemplo, no meu exemplo acima, todos os valores retornados seriam do tipo any .

Também acho que o ... extra é muito difícil de ler.
Como a ideia @felixfbecker , não vejo necessidade de fazer:

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

A primeira coisa que vem à mente ao ler apply<...T, é que ele é um operador de spread, mas na verdade não faz nenhum spread.

@Griffork , em seu exemplo T ainda seria [string, int] .
Isso é o que @felixfbecker significa "em vez disso, habilitar o compilador TS para inferir um tipo de tupla para T", pelo menos da forma como eu entendo.

Força todos os parâmetros a serem tratados como o mesmo tipo, e eles não podem ter uma verificação de tipo mais específica. Por exemplo, no meu exemplo acima, todos os valores retornados seriam do tipo qualquer.

@Griffork Não, na minha opinião ele inferiria um tipo de tupla para o array args , dando a cada parâmetro seu próprio tipo por sua posição na tupla. ...args: T[] forçaria todos eles a serem do mesmo tipo T , mas ...args: T (que atualmente é um erro de compilação) inferiria um tipo de tupla para T .

A primeira coisa que vem à mente ao ler apply <... T, é que ele é um operador de propagação, mas na verdade não faz propagação de forma alguma.

@unional concorda, é exatamente vem a confusão.

@unional
Eu li isso como o operador de spread também, eu li como "espalhe esse tipo toda vez que for usado".
Para mim, lendo isso

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

Eu esperaria que T fosse um array algo (por exemplo, string[] ).

E lendo isso:

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

Eu esperaria que todos os args pudessem ser atribuídos ao tipo T (que é um tipo, como string ).

O objetivo da proposta acima era evitar implicitamente tornar os genéricos capazes de representar uma quantidade arbitrária de tipos.

@felixfbecker
Editar:
Ah ok. Ainda não acho isso intuitivo.

Eu esperaria que T fosse um array algo (por exemplo, string []).

Uma tupla é "algo array", é apenas um array com um comprimento fixo e tipos específicos para cada elemento, por exemplo, [string, number] (vs (string | number)[] , que é desvinculado e não declara qual elemento tem o quê modelo).

Bem, então o que você digita se realmente deseja esse comportamento?

Não tenho certeza de qual comportamento exatamente você está se referindo, mas presumo "forçar todos os parâmetros a serem do mesmo tipo", o que seria realizado por ...args: T[] .

Eu li isso como o operador de spread também, eu li como "espalhe esse tipo toda vez que for usado".

É por isso que acho confuso.
Quando você espalha, você apenas o faz, você não declarará algo que é "distribuível":

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

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

Sim. Se você quiser declarar que um argumento de tipo genérico precisa ser "espalhado" (o que por especificação ES significa apenas que deve ser iterável), já temos uma maneira de expressar isso no TypeScript com extends :

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

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

é claro, no caso de um parâmetro resto, sabe-se que não é apenas Iterable, mas um Array.

Que, o que estamos dizendo já foi proposto: https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189703556

Mas o resto é muito longo para consumir de uma só vez. 🌷

Se a concatenação de tupla estiver chegando, podemos implementar números de igreja! Viva!

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'];

E se a recursão do tipo condicional estiver funcionando, podemos criar uma tupla com o comprimento desejado:

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

Não presumo que li todo este tópico, mas se ter ...T em parâmetros genéricos é confuso,
por que não tentar espelhar ainda mais a sintaxe de desestruturação de nível de valor, de modo que usando [...T] em um
A posição do argumento de nível de tipo destrói o tipo se for uma tupla de nível de tipo? Isso também exigiria
permitindo a digitação de parâmetros restantes com tuplas, o que tornaria o seguinte equivalente no local da chamada
em TypeScript:

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

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

INB4 "mas isso é emissão dirigida por tipo" - não. Isso não muda a emissão, o first ainda tem dois
argumentos distintos emitidos, o second ainda terá um argumento de resto. A única coisa que muda é
que no local da chamada, o TypeScript verificará se os parâmetros de second correspondem, em ordem, à tupla
[number, string] .

De qualquer forma, supondo que admitamos a sintaxe [...Type] , poderíamos escrever apply seguinte maneira:

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`

A sintaxe [...Type] se comportaria inteiramente como uma desestruturação de nível de valor, permitindo a divisão
e juntando tuplas de nível de tipo, conforme necessário:

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];

Um exemplo de como digitar a função curry usando isto:

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`

Suponho que também seria útil poder dizer que algum argumento de tipo é uma tupla de nível de tipo
De algum tipo. A questão, então, seria como - considerando uma vez que a atribuibilidade de tupla de 2.7 (eu acho?) Leva
em consideração o comprimento da tupla - para expressar o conceito de _qualquer tupla no nível do tipo_. Mas talvez algo como
[...] poderia funcionar? Não tenho uma opinião forte, mas seria bom se o conceito tivesse nome.

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

Nesse caso, a sintaxe acima de [...ArgsT] poderia ser basicamente uma abreviação para ArgsT extends [...] ,
ter o uso de desestruturação de nível de tipo implica uma restrição no tipo para ser uma tupla de nível de tipo.

Pensamentos?

@jaen :

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

Sim, parece # 4130. Eu tentei algo em # 18004, mas minha abordagem era um pouco hacky (nós sintéticos).

Ao expressar qualquer tupla, vi alguém usando any[] & { 0: any } , o que acho que funciona até esvaziar a tupla do tipo fwiw. Eu não me incomodei muito pessoalmente, principalmente apenas me conformei com any[] .

RxJS precisa disso em todo lugar. Mais criticamente para Observable.prototype.pipe , para o qual atualmente temos muitas sobrecargas, mas sempre me pedem para adicionar "apenas mais um nível".

Para apoiar @benlesh , usamos RXJS extensivamente e precisamos dele para funções de pipe.

Sou o autor de ppipe , que, como as funções de pipe no RXJS, precisa disso. Acho que vejo um padrão aqui ^^

Eu sou o autor de runtypes , e esse recurso é desesperadamente necessário para expressar uniões e interseções. A única solução alternativa (incompleta) são as sobrecargas gigantescas:

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

🤢

Eu reescrevi os tipos de ramda , que também precisam disso para, por exemplo, pipe , lentes e currying.
Precisávamos codegen como currying feito diretamente mantendo sobrecargas incontroláveis. Nosso tipo path estendeu por mais de mil linhas, ponto em que descobrimos que o desempenho do tipo de sobrecarga também se tornou problemático.

O problema de inferir e aplicar os argumentos restantes foi resolvido?

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)
}

Se o tipo de T em apply(example, ['0', 1, 2, 3]) fosse inferido como [string, number[]] , a chamada para aplicar geraria um erro.

Isso significa que o tipo de T é realmente

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

ou

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

Uma besta estranha, de fato, mas dado como ({0: string} & Array<number>)[0]
atualmente resolve para string [1] parece possível codificar sem muitas mudanças
para o sistema de tipo.

[1] Isso é um bug, deveria realmente ser string | number ?

Desculpe incomodar 36 participantes dessa questão (dica: use isso ), mas como podemos monitorar se isso ainda está sendo considerado, se está no roteiro, etc?

É triste que ninguém tenha sido atribuído a ele ainda depois de 2 anos e meio, parece ser um recurso muito importante :(

PS: Eu li algumas dezenas de comentários, tentei Cmd + F etc, não encontrei esta informação.

@brunolemos há uma referência aos tipos variadic na última reunião de design
https://github.com/Microsoft/TypeScript/issues/23045
Para criar esse recurso, eles precisam primeiro criar conceitos e conceitos mais primitivos de forma iterativa e, quando houver bases suficientes, tenho certeza de que eles irão adicioná-lo a qualquer marco

Eu não posso fazer isto

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

É uma questão de # 14174, mas como uma relação

@kgtkr para referência, veja o truque de @fightingcat para fazer o compilador ignorar a recursão.

obrigado

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

Hmm, eu tenho uma pergunta. Eu tenho um código como este para lidar com 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...
                }
            });
        }
    };
};

Eu uso como segue:

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

Funciona como esperado - com digitação adequada, manipulação de estado e assim por diante, mas há alguma maneira de reescrever de uma maneira mais curta sem esperar por tipos variáveis? Porque me sinto um pouco idiota com relação a isso agora.

Com o 3.0 agora é possível declarar argumentos de descanso como tupple

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

Mas é possível obter o inverso para obter o tipo de tupla de argumentos da função dada?

Algo como Arguments<foo> seria bom.

@whitecolor que tal isso?

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

com TS 3.0 podemos fazer isso agora

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);
  };
}

mas temos um pequeno problema, temos que declarar funções que retornam eventos tupples para parâmetros simples e também lidar com void de alguma forma e temos que declarar o tipo de retorno, caso contrário, é inferido como array :)

https://www.typescriptlang.org/play/index.html#src =% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0A% 0D% 0Afunction% 20foo0 () % 3A% 20void% 20% 7B% 0D% 0A% 20% 0D% 0A% 7D% 0D% 0A% 0D% 0Afunction% 20bar0 ()% 3A% 20void% 20% 7B% 0D% 0A% 0D% 0A% 7D % 0D% 0A% 0D% 0Afunction% 20foo1 (a% 3A% 20string)% 3A% 20% 5Bstring% 5D% 20% 7B% 0D% 0A% 20% 20return% 20% 5Ba% 5D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0Afunction% 20bar1 (a% 3A% 20string)% 3A% 20% 5Bstring% 5D% 20% 7B% 0D% 0A% 20% 20return% 20% 5Ba% 5D% 3B% 0D% 0A % 7D% 0D% 0A% 0D% 0Afunction% 20foo2 (a1% 3A% 20string% 2C% 20a2% 3A% 20boolean)% 3A% 20% 5Bstring% 2C% 20boolean% 5D% 20% 7B% 0D% 0A% 20% 20retorno% 20% 5Ba1% 2C% 20a2% 5D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0Afunção% 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% 0Afunção% 20bar2 (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% 0A% 0D% 0Afunction% 20compor% 3CX% 20extends% 20any% 5B% 5D% 2C% 20Y% 20extends% 20any% 5B% 5D% 2C% 20Z% 20extends% 20any% 5B% 5D% 3E ( % 0D% 0A% 20% 20f% 3A% 20 (... args% 3A% 20X)% 20% 3D% 3E% 20Y% 2C% 0D% 0A% 20% 20g% 3A% 20 (... args% 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% 20return% 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% 20compor (% 0D% 0A % 20% 20foo0% 2C% 0D% 0A% 20% 20bar0% 0D% 0A)% 3B% 0D% 0A% 0D% 0Aconst% 20baz21% 20% 3D% 20compor (% 0D% 0A% 20% 20foo21% 2C% 0D % 0A% 20% 20bar1% 0D% 0A)% 3B% 0D% 0Aconst% 20baz2% 20% 3D% 20compor (% 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
Use tipos condicionais:

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, mas isso é meio hacky com aqueles as any :) Eu acho que seria legal se o sistema de tipos suportasse isso sem hacks

Bem, os tipos no corpo da função podem ser várias coisas, não há realmente muito que possa ser feito sobre isso - você poderia fazer algo como (f as (...args: any[]) => Y) vez disso, mas acho que isso reduz a clareza sem motivo real

Se os tipos variados se concretizarem, eu seria capaz de generalizar algum código TypeScript que escrevi para meu próprio projeto, que me permite definir totalmente a forma da minha API REST e impor essa forma aos tipos do servidor Node e JavaScript correspondentes biblioteca cliente para ele.

Generalizar isso me permitiria simplificar meu próprio código, bem como fazer o mesmo para APIs arbitrárias, e ir além e provavelmente gerar definições Swagger para outros clientes de linguagem também ... poderia ser útil para outros! Apenas sonhando alto embora haha

@kgtkr : Isso parece ótimo! :)

O tipo Pipe trava o playground de TS para mim (embora outros funcionem bem), eu acho que ele precisa do TS mais recente?

TS também mostra alguns erros de profundidade de recursão - parece que @isiahmeadows abriu # 26980 para isso.

@ tycho01

O tipo Pipe trava o playground de TS para mim (embora outros funcionem bem), eu acho que ele precisa do TS mais recente?

Ele trava para mim também, e eu tive que invadir o Devtools para forçá-lo a lançar um erro para escapar dele.

TS também mostra alguns erros de profundidade de recursão - parece que @isiahmeadows abriu # 26980 para isso.

Isso é por algo relacionado, mas diferente: levantar uma restrição com tipos condicionais por dois motivos:

  • Para facilitar a execução de trabalhos mais complexos, como iteração de lista. Também estabeleceria a estrutura para abrir coisas como matemática de inteiros em nível de tipo sem travar o compilador ou acabar em alguma bagunça ad-hoc.
  • Para abordar de forma mais adequada a questão do que torna o sistema de tipos do TS Turing-completo, de modo que possa ser potencialmente reificado com ferramentas que o alavancam ou removido aplicando terminação comprovável no futuro.

Se a remoção de tipos indexados não for suficiente para tornar o sistema de tipos não Turing-completo como está, sinta-se à vontade para deixar um comentário aqui para que eu possa atualizá-lo de acordo. (Não proponho removê-lo, é claro. Eu apenas proponho lidar melhor com ele internamente para alertar as pessoas sobre loops infinitos em potencial.)

Pensando bem, esse tipo de Pipe parece uma maneira muito complicada de fazer (vs...: Params<T[0]>) => ReturnType<Last<T>> . Qualquer iteração além disso (além das verificações de parâmetros intermediários) provavelmente se torna mais útil com tipos de retorno dependentes de entrada.

@ tycho01 É tentar coisas do tipo como este , em que o tipo é basicamente este:

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

Você tem que iterate os parâmetros individualmente para digitá-lo corretamente, já que os parâmetros subsequentes parâmetros são dependentes de parâmetros anteriores dos valores de retorno, e você também tem que conta para o primeiro parâmetro e valor de retorno final (que @kgtkr é não) . Minha versão "aprimorada" em # 26980 fez uma otimização para usar um acumulador, então eu estava iterando muito menos os argumentos, mas também o tornou mais correto.


Se você olhar para o meu em # 26980, é um pouco mais claro o que deveria estar fazendo (menos perseguição de número), e isso é parte do motivo pelo qual eu preenchi essa solicitação de recurso.

@ tycho01 @kgtkr BTW, atualizei esse bug com um PipeFunc snippet corrigido , copiado aqui para sua conveniência:

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 não bate no playground, BTW. Na verdade, ele verifica o tipo e o faz muito rapidamente.

Eu não testei em um tipo potencial de _.flow ou _.flowRight ainda, mas isso deve funcionar como um ponto de partida.

@ tycho01
obrigatório
typescript @ next
3.0.1 / 3.0.2 não funciona

Graças a este tópico, eu fiz isso

Pessoal, parem de postar informações pouco relevantes para a discussão deste assunto. Há muitas pessoas acompanhando essa discussão, porque queremos tipos variados. Recebi cerca de 10 ou mais e-mails nos últimos dias que são irrelevantes para os motivos pelos quais estou acompanhando este problema.
Espero que haja outros de acordo comigo. Até este ponto, eu só esperava que parasse, porque não queria contribuir para o spam. Mas, falando sério, basta.
PS Desculpe por esta notificação, para qualquer um que está tão cansado deles quanto eu

@Yuudaari Vou apontar que digitar _.flow Lodash, _.compose Ramda, etc. é uma das coisas que impulsionam esse bug, e uma digitação bem-sucedida faz parte da solução desse problema. Na verdade, esse é um dos motivos listados na descrição do problema original.

Realmente, neste ponto, eu sou da opinião de que 99% dos problemas que existem hoje com variáveis ​​são de ergonomia, não de funcionalidade. Nós podemos escrever Function.prototype.bind e Promise.all perfeitamente com uma mistura de tipos indexadas, tipos condicionais e recursão (você pode fazer uma repetida Append iteração da lista para Function.prototype.bind , e Promise.all seria uma iteração simples + Append ), mas é muito estranho e clichê fazer isso.

Não tentando aumentar o ruído aqui, apenas explicando que o material que começa aqui está tecnicamente no assunto, pois diz respeito a alguns dos motivos pelos quais o bug existe, mesmo que não sejam aqueles com os quais você esteja pessoalmente preocupado.

Acho que as pessoas que esperavam por anúncios perderam a grande notícia - descobriram que as funções agora possíveis Concat<T, U> são exatamente como [...T, ...U] .

A sub-discussão Pipe trata de demonstrar a funcionalidade que pedimos aqui. É sobre como chegar ao ponto deste tópico, hoje.

Acho que isso significa que não seria pior fechar este tópico, então talvez este seja um bom momento para perguntar - o que as pessoas ainda querem desta proposta?

[é] apenas muito estranho e clichê

A maioria dos tipos que usam isso usará recursão eles próprios, então aqueles que os escreverem certamente estarão familiarizados com eles, enquanto os usuários finais provavelmente apenas usarão bibliotecas com tipos predefinidos e escreverão seu front-end, sem precisar saber que existe uma iteração TS.

Nesse ponto, talvez esta proposta possa melhorar principalmente o desempenho?

Em primeiro lugar, o uso de objetos de mapa para enganar o sistema de tipos a fazer recursão é intencional? Parece muito hackeado para mim. Se eu usar uma funcionalidade como essa (eu uso, mas isso é irrelevante), ela não estará sujeita a ser interrompida posteriormente?

Em segundo lugar, usar essas soluções alternativas simplesmente não é ... amigável. Não é muito legível (especialmente para aqueles que não o escreveram) e, como resultado, parece péssimo de manter.

Por que eu iria querer voltar para uma proposta que adiciona os mesmos recursos, de forma intencional, legível e sustentável, só porque há uma solução alternativa para eles?

Não acho que a existência dessa solução alternativa faça com que essa proposta seja considerada um açúcar sintático, mas mesmo que fosse, por que eu não iria querer um açúcar sintático para essa bagunça?

@Yuudaari

Editar: Adicionar link para contexto.

Em primeiro lugar, o uso de objetos de mapa para enganar o sistema de tipos a fazer recursão é intencional? Parece muito hackeado para mim. Se eu usar uma funcionalidade como essa (eu uso, mas isso é irrelevante), ela não estará sujeita a ser interrompida posteriormente?

Dê uma olhada no bug que preenchi recentemente: # 26980. Você não está sozinho em questionar o padrão. É um pouco fora do assunto aqui, mas fique à vontade para falar aqui.

Observe que há um pouco de matemática envolvida em como descobrir se algo recursivo termina (uma das principais razões por que é tão nuançado em primeiro lugar).

Em segundo lugar, usar essas soluções alternativas simplesmente não é ... amigável. Não é muito legível (especialmente para aqueles que não o escreveram) e, como resultado, parece péssimo de manter.

Por que eu iria querer voltar para uma proposta que adiciona os mesmos recursos, de forma intencional, legível e sustentável, só porque há uma solução alternativa para eles?

Não acho que a existência dessa solução alternativa faça com que essa proposta seja considerada um açúcar sintático, mas mesmo que fosse, por que eu não iria querer um açúcar sintático para essa bagunça?

Existe uma maneira simplificada de iterar tuplas no caso comum do que é efetivamente Array.prototype.map , mas isso era basicamente inútil para minhas necessidades (eu precisava de um acumulador).

Eu pessoalmente gostaria de açúcar sintático para estes:

  1. Concatenando duas listas por meio de [...First, ...Second] .
  2. Anexando valores por meio de [...Values, Item] .
  3. Extraindo o último elemento por meio de T extends [...any[], infer Last] .
  4. Extraindo a cauda por meio de T extends [A, B, ...infer Tail] .

Combine isso com # 26980, e eu poderia transformar os tipos acima neste:

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;

Mas é isso aí. Não vejo muita utilidade para qualquer outro açúcar sintático, uma vez que quase tudo aqui lida apenas com tuplas, e os objetos já têm, em grande parte, tudo o que você precisa para operações semelhantes.

Em primeiro lugar, o uso de objetos de mapa para enganar o sistema de tipos a fazer recursão é intencional? Parece muito hackeado para mim. Se eu usar uma funcionalidade como essa (eu uso, mas isso é irrelevante), ela não estará sujeita a ser interrompida posteriormente?

Acho que a palavra oficial é algo como "não faça isso". @ahejlsberg disse :

É inteligente, mas definitivamente leva as coisas muito além do uso pretendido. Embora possa funcionar para pequenos exemplos, ele terá uma escala horrível. Resolver esses tipos profundamente recursivos consome muito tempo e recursos e pode, no futuro, entrar em conflito com os governadores de recursão que temos no verificador.

Não faça isso!

☹️

@jcalz Mais uma razão para a existência do # 26980?

Quando comecei a usar o TS apenas no intervalo deste ano, minha inclinação era escrever _só isso_! ( ...T ) na esperança de que seria a sintaxe para tuplas de variante de tipos variáveis. Bem, espero que isso entre :)

Acabei de encontrar um novo uso para [...T, ...U] : digitar corretamente os construtores de HTML. Para um exemplo concreto, os filhos de <video> devem ser os seguintes:

  • Se o elemento tiver um atributo src :

    • Zero ou mais elementos de <track>

  • Se o elemento não tiver um atributo src :

    • Zero ou mais elementos <source>

  • Zero ou mais elementos de acordo com o modelo de conteúdo do pai, exceto audio ou video elemento descendente é permitido.

Isso basicamente equivale a este tipo, mas não há como expressar isso no TypeScript hoje:

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

3,5 anos: /

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);
}

Vejo apenas sobrecargas mencionadas brevemente na seção de perguntas abertas da proposta, mas definitivamente algo que encontrei e seria excelente para ver uma solução ou uma proposta, por exemplo:

  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

atualmente, isso não funciona e apenas digita promisify como (ctx: ICtx, filter: FilterQuery<TSchema>) => Promise<TSchema[]> que está perdendo informações dessas assinaturas.

AFAICT, a única solução real para isso é basicamente criar uma função de promessa e especificar manualmente todos os tipos possíveis para essa variante - a única maneira de realmente fornecer uma assinatura honrada da variante encapsulada é não especificar sobrecargas e apenas especificar a assinatura de implementação, mas não há como permitir que o chamador saiba qual dos tipos de retorno ele deve esperar com base nos argumentos que passam se você especificar a assinatura dessa forma.

isso é exacerbado pelo fato de que os parâmetros restantes podem ser apenas o último parâmetro (ou seja, (cb, ...args) é válido, mas não (...args, cb) , então mesmo se a assinatura for internamente um tipo de união, você não pode realmente espalhar as coisas corretamente - por exemplo, seria bastante simples se cb fosse sempre o primeiro argumento a digitar promisify como function promisify<T, V extends any[]>(fn: (cb: (err: Error | null, res?: T) => void, ...args: V)): (...args: V) => T e você pudesse pelo menos obter tipos de união para assinaturas com a mesma resposta de retorno, mas porque é o parâmetro final afaict não há muito que possa ser feito aqui

@ Qix- Seu cenário foi habilitado por # 24897. Foi implementado em TS 3.0.

@ahejlsberg Oof! Incrível, obrigado ♥ ️

Demorou muito tempo esperando ... Mas hoje é possível escrever tipos variados. TS é maduro o suficiente para escrever tipos complexos que funcionam. Então, dediquei um tempo para escrever tipos de curry, concat , compose e pipe para Ramda.

E agora são fornecidos com o cinto de ferramentas ts .

No entanto, esta proposta é um bom açúcar sintático para tornar as manipulações de tuplas comuns muito mais fáceis.

você já tem no medium.com? URL?

Existe o artigo original no Medium, mas o bônus não está incluído nele, está no repo. Também explica como criei todas as pequenas ferramentas para compor, canalizar e curry: smile:

@ pirix-gh mas não são genéricos variados como nesta proposta

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

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

@goodmind Sim, não é, é mais emulado. Então você pode emular ... assim:

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

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

É o mesmo que:

declare function m<...T, ...U>(): [...T, ...U]

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

Enquanto isso, enquanto espera por esta proposta: hourglass_flowing_sand:

@ pirix-gh você pode por favor ajudar com a função de embrulho 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) // {}

Eu estava tentando usar a abordagem de composição, mas não consegui. Você pode sugerir uma maneira adequada, por favor?

Isso acontece porque quando você extrai os parâmetros / retorno de fn que são dependentes de genéricos, o TS os infere para seu tipo mais próximo (neste caso T será any ). Portanto, não há como fazer isso no momento. Nossa melhor esperança é esperar que esta proposta seja combinada com https://github.com/Microsoft/TypeScript/pull/30215. Portanto, você terá que escrever sobrecargas de tipo.

Ou talvez pudéssemos encontrar uma maneira de preservar / mover os genéricos de forma que pudéssemos:

declare function ideal<...T>(a: T[0], b: T[1], c: T[2]): T

ideal('a', 1, {}) // T = ['a', 1, {}]

Dessa forma, reconstruiríamos fn de pedaços dele. A parte que falta hoje é a parte genérica como @goodmind apontou.

@ pirix-gh Se não me engano, você pode simplesmente fazer isso para conseguir o que você tem lá em cima:

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 Não exatamente, porque o que propus funciona para uma quantidade ilimitada de argumentos. Mas talvez pudéssemos fazer isso também, com o que você propôs (não vi na proposta):

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 Os argumentos do tipo A , B e C em seu exemplo não são usados.

-declare function MyFunction<A, B, C, ...Args>(...[a, b, c]: Args): Args
+declare function MyFunction<...Args>(...[a, b, c]: Args): Args

Mesmo se os tipos variáveis ​​fossem implementados, os dois exemplos anteriores provavelmente gerariam um erro de compilação, qual é o ponto dos tipos variáveis ​​se você quiser apenas três argumentos.

Seus exemplos devem mostrar por que a variadic é necessária, se eles podem ser feitos com o código TS existente, então isso não ajuda em nada a causa.

@goodmind Sim, não é, é mais emulado. Então você pode emular ... assim:

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

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

É o mesmo que:

declare function m<...T, ...U>(): [...T, ...U]

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

Enquanto isso, enquanto espera por esta proposta ⏳

Onde você conseguiu Concat<> ?

Edit: Não importa encontrou o código-fonte.

@ pirix-gh então tentei fazer isso com suas sugestões, mas não consegui descobrir.

~ O problema é que estou tentando estender os parâmetros do ctor de uma classe e funciona a ponto de ter uma matriz de tipos, mas não consigo distribuí-los para os parâmetros do ctor. ~

Class Test {
  constructor(x: number, y: string) {}
}
let ExtendedClass = extendCtor<[number, string], [number]>(Test);

let instance = new ExtendedClass(1, '22', 2);

Update: Não importa que também funcionou usando um spread na função ctor.

Aqui está o link da solução

O único problema é que o TS trava quase todas as vezes: |
e é isso que o TypeScript diz Type instantiation is excessively deep and possibly infinite.ts(2589)

Atualização 2:
Eu consegui colocando o novo tipo no começo, mas seria bom poder mesclar esses 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');

em oposição a:

let MyClass1 = extendClass<typeof Test, string>(Test);

let instance = new MyClass1('check', '22', 2);

Link para a solução final.

Se bem entendi Object.assign pode ser declarado algo como o seguinte para suportar totalmente os 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>
}

Há algum motivo para ele ser declarado de maneira diferente no lib.d.ts do TypeScript?

Ele não está usando tipos condicionais recursivos porque eles não são suportados (consulte # 26980 para uma discussão sobre isso, ou este comentário nos dizendo para não fazer isso). Se alguém estiver disposto a usar os tipos de retorno de interseção atuais, há # 28323.

@jcalz Eu criei um teste pesado que mostra o tipo Minus em ação. Ele executa Minus 216000 vezes em menos de 4 segundos. Isso mostra que o TS pode lidar muito bem com tipos recursivos. Mas isso é bem recente.

Porque? Isso é graças a Anders: tada: (https://github.com/microsoft/TypeScript/pull/30769). Ele me permitiu mudar de tipos condicionais para condições indexadas (como um switch). E, na verdade, melhorou o desempenho em x6 para o ts-toolbelt. Muito, muito obrigado a ele.

Então, tecnicamente, poderíamos reescrever o tipo de @kimamula com segurança com o ts-toolbelt. A complexidade segue 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'},
]>

A lib também torna a recursão segura com Iteration que irá prevenir qualquer overflow do TypeScript. Em outras palavras, se I ultrapassar 40 então estourará e Pos<I> igual a number . Parando assim a recursão com segurança.

Um tipo recursivo semelhante que escrevi ( Curry ) é enviado com Ramda e parece que está indo bem.

A propósito, agradeci (@jcalz) na página do projeto por todos os seus bons conselhos.

Não tenho certeza se # 5453 é o melhor lugar para ter essa discussão ... devemos falar sobre isso em # 26980 ou há uma localização mais canônica? Em qualquer caso, eu adoraria ter uma maneira oficial e com suporte de fazer isso, que não implodirá em versões subsequentes do TypeScript. Algo que está incluído em seus testes de linha de base para que, se quebrar, eles consertem. Mesmo que o desempenho seja testado para ser bom, eu desconfio de fazer isso em qualquer ambiente de produção sem uma palavra oficial de alguém como @ahejlsberg.

Algo que está incluído em seus testes de linha de base para que, se quebrar, eles consertem.

Acho que temos algo muito próximo usado internamente

@weswigham me perdoe por ser denso, mas você pode me mostrar como o tipo destacado é recursivo? O que me preocupa é a forma

type Foo<T> = { a: Foo<Bar<T>>, b: Baz }[Qux<T> extends Quux ? "a" : "b" ]

ou qualquer uma das variantes que vi. Se estou faltando alguma coisa e isso recebeu algum tipo de luz verde, por favor, alguém me avise (e me ensine como usá-lo!)

Oh, justo - é diferente nesse aspecto, sim. Acabei de dizer o padrão "objeto imediatamente indexado para selecionar tipos" e percebi que tínhamos _that_.

Eu tenho uma pergunta.

Quanto do material proposto aqui ainda é relevante? Esta edição foi aberta há 4 anos e sinto que várias coisas mudaram desde então.

Do meu comentário aqui,
https://github.com/microsoft/TypeScript/issues/33778#issuecomment -537877613

Eu disse,

TL; DR, tipos de tupla, argumentos restantes, tipos de matriz mapeada, inferência de tupla para argumentos não remanescentes, aliases de tipo recursivo = nenhuma necessidade real de suporte de argumento de tipo variável

Mas estou curioso para ver se alguém tem um caso de uso que simplesmente não pode ser habilitado pelas ferramentas existentes

Até obtermos uma versão oficialmente abençoada de Concat<T extends any[], U extends any[]> então isso ainda é relevante. Não acho que o próximo recurso de referência de tipo recursivo nos dê isso, mas ficaria feliz em ser informado (com autoridade) de outra forma.

Não temos implementações de Concat<> já?

Ou a frase-chave aqui é "oficialmente abençoada"?

Porque minha afirmação é que você pode basicamente fazer tudo (ou quase tudo?) Que quiser no momento, mesmo que não seja totalmente "oficialmente abençoado".

Mas eu acho que "oficialmente abençoado" deve ser sempre preferido ... Bom ponto. Estou muito acostumado a (ab) usar aqueles aliases de tipo recursivo

Em geral, prefiro uma sintaxe real e elegante, de modo que toda vez que faço algo assim, não preciso ficar explicando aos meus colegas de equipe (geralmente juniores) o que está acontecendo com os tipos especificados confusamente que o abuso do status quo exige. Essa confusão prejudica minha capacidade de evangelizar o TypeScript, ou pelo menos esses usos dele, na minha organização.

Grande 👍 nisso!

Este recurso é MUITO importante.

@AnyhowStep

Porque minha afirmação é que você pode basicamente fazer tudo (ou quase tudo?) Que você poderia querer no momento

Não vejo nenhuma evidência de que digitar um parâmetro de propagação com vários parâmetros únicos ao lado é facilmente alcançável hoje em TS.

@matthew-dean não é totalmente verdade. Aqui está um exemplo que você pode alcançar até certo ponto.

Pelo que entendi, o TS procura digitar o máximo possível de programas JS vanilla. Aqui está um quebra-cabeça:

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);

Eu esperaria que o tipo não fosse mais complexo do que [number, ...T, number, ...T, number] . Se tivermos que escrever 20 linhas de algum código estranho que abusa de um bug na verificação para ter um tipo de retorno adequado na última linha, este problema não foi resolvido.

@ polkovnikov-ph O tipo atualmente é inferido como: [number, ...any[]] , o que não ajuda em nada.

Também gostaria de observar que não temos que honrar a décima regra de Greenspun por 15 anos como C ++ fez, porque C ++ já passou por todos os Head<> Cons<> para nós, e desenvolveu uma sintaxe de template variadic muito prática e limpa. Podemos economizar (centenas de anos) o tempo do desenvolvedor e apenas pegar as melhores partes de lá.

Por exemplo, tipos variáveis ​​têm um tipo diferente em C ++, então você não pode usar variáveis ​​de tipo variadico onde o tipo é esperado, ao contrário de um tipo que extends any[] em TS. Isso permite que o C ++ mapeie / feche as tuplas mencionando a variável de tipo variável dentro de alguma expressão incluída no operador de reticências. Esta é basicamente uma 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 a sintaxe de reticências proposta em vez de extends any[] é usada no exemplo não apenas por razões estéticas, mas porque

type A<T> = any[]
type B<T extends any[]> = [...A<T>]
type C = B<[1, 2]>

já é um programa TS válido. C acaba sendo any[] vez de [any[], any[]] que o tipo de variável mapeado geraria.

@DanielRosenwasser Peço desculpas por

Como um aparte, apenas ter operações de propagação de nível de tipo para tipos de tupla seria uma grande ajuda para minha equipe, mesmo que a falta de tipos variáveis ​​signifique que eles não podem ser usados ​​com parâmetros de tipo. Em nosso domínio de problema, "arrays com alguma estrutura" são muito comuns. Simplificaria muito as coisas para nós se esta operação funcionasse:

type SharedValues = [S1, S2, S3];
type TupleOfSpecificKind = [V1, ...SharedValues, V2];

@sethfowler se você tiver alguns exemplos do que deseja expressar, isso é sempre útil para nós. Caso contrário, você pode estar interessado em https://github.com/microsoft/TypeScript/issues/26113

@DanielRosenwasser Claro, posso tornar as coisas um pouco mais concretas. Vou manter os detalhes fora disso, mas em um alto nível, você pode pensar em nosso projeto como a geração de um fluxo de operações gráficas e outros eventos semelhantes que são enviados a um servidor remoto. Por motivos de eficiência, precisamos representar essas operações na memória em um formato que seja diretamente traduzível em sua forma serializada. Os tipos desses eventos ficam parecidos com estes:

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

Gostaríamos de ser capazes de expressar esses tipos mais assim:

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>];

Temos um grande número desses comandos, portanto, apenas ter a propagação no nível do tipo resultaria em um código muito mais sustentável. Ter tipos variáveis ​​para que pudéssemos escrever funções de nível de tipo como Repeated<> e RepeatedUpToTimes<> (que seriam uniões definidas recursivamente de tipos de tupla neste exemplo) iria ainda mais longe para simplificar as coisas.

Também seria imensamente útil ter suporte para coisas como concatenação segura de tipos de tipos de tupla (conforme discutido no OP). Para usar os tipos acima, atualmente temos que construir a tupla inteira em uma única expressão literal de tupla. Não podemos construí-lo em partes e concatená-los juntos agora. Em outras palavras, as operações abaixo não funcionam hoje, mas realmente gostaríamos que funcionassem.

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];

Esperançosamente, esses exemplos são úteis!

Você pode facilmente escrever um tipo Concat<> e fazer um tipo Concat3<> , usando Concat<> .

Então,

type OpCircle = Concat3<Colors, Vertex3D, [number] /* radius */>;

Do acima, você pode escrever uma função concat com sobrecargas para 2,3,4,5,6, etc. número de argumentos.

É até possível escrever um Concat <> impl que pega uma tupla de tuplas e concatra as tuplas. Um tipo Concat <> var-arg.


Não é algo que não possa ser feito hoje. Isso pode ser feito, mesmo que exija que você escreva um tipo recursivo e uma função auxiliar.

Toda vez que eu uso esses tipos recursivos no vscode, o TS quer matar a cpu ou simplesmente trava! Esse é o principal problema, acho que o TS está ficando muito pesado sem um bom motivo.

Talvez as pessoas que escrevem os tipos não estejam fazendo o suficiente para otimizá-los?

Não quero enviar spam ou arrastar conversas antigas que não agregam muito valor a este tópico, mas me lembro de ouvir em algum momento que o cálculo de tipos condicionais recursivos é caro em javascript (encontrei aqui neste tópico )

Dito isso (eu sei que pode parecer loucura), talvez seja hora de reescrever o TS em outra linguagem para poder dar um impulso ao sistema de tipos, já que o TS já cresceu tanto que é razoável pedir um melhor.

Você pode facilmente escrever um tipo Concat<> e fazer um tipo Concat3<> , usando Concat<> .

Você poderia fornecer uma implementação para o tipo Concat<> que está descrevendo? É fácil escrever Cons<> , mas Concat<> não é tão fácil (para mim) e adoraria ver o que você está imaginando.

Com relação a Concat3<> , Concat4<> , etc., a esperança é que, a longo prazo, não precisaremos escrever dezenas de variantes como essas, porque teremos tipos variáveis. 🙂 Se uma boa implementação deles for possível hoje, porém, isso seria uma medida paliativa razoável.

Para concatenação regular de duas tuplas,
https://github.com/AnyhowStep/ts-trampoline-test (usa trampolins para concatear tuplas muito grandes, das quais a maioria das pessoas não precisa)

Concat3 seria apenas Concat, C>

O VarArgConcat seria,
VarArgConcat<TuplesT extends readonly (readonly unknown[])[], ResultT extends readonly unknown[] = []>

Enquanto a tupla não está vazia, VargArgConcat<PopFront<TuplesT>, Concat<ResultT, TuplesT[0]>>

Se TuplesT estiver vazio, retorna ResultT

Obviamente, a recursão ingênua levará a erros de profundidade máxima com tuplas de comprimento decente. Então, use a técnica de recursão em ts-toolbelt ou use trampolins com copiar e colar na profundidade desejada


Esse repo ao qual vinculei usa Reverse<> para implementar Concat<> . Copiei e colei o código de outro projeto no qual estou trabalhando.

Eu concordo que esse seria um recurso extremamente útil.

Digamos que temos um tipo T :

type T = {
  tags: ["a", "b", "c"];
};

E queremos criar um novo tipo, com uma tag adicional "d" , adicionada à tupla T["tags"] . Os usuários podem inicialmente tentar criar este utilitário ( WithTag<NewTag, ApplyTo> ) da seguinte maneira:

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
  tags: [Tag, ...Target["tags"]];
};

Tentar fazer isso atualmente gera o erro A rest element type must be an array type . Os usuários podem pensar que trocar string[] por Array<string> faz diferença, mas não faz. Nem usar uma condição + never :

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
- tags: [Tag, ...Target["tags"]];
+ tags: Target["tags"] extends string[] ? [Tag, ...Target["tags"]] : never;
};

Parque Link: https://www.typescriptlang.org/play?#code/C4TwDgpgBA6glsAFgFQIYHMA8AoKU3pQQAewEAdgCYDOU1wATnOegDS76oPoTBGkUaUAN7AM1AFx1GzdAG0AugF9sAPigBeTt15QAZCI5j0kqHIKsoAOhtodwOQCJj1RwoUBubEq -ZQkKABJTUM8FyknVEdLRwAjaKhHAGM3Lx9sP3BoZBD4JAJMR0oEwNVfAHoAKkrcSqgAUWJIJLJKKAADZHaoYAB7KFjoXoAzHsRoYd6AGynegHdZHqyrWqhV4VWe8QjHKJj4mJSY4s9VlShK8qA

Problemas Relacionados ::

Infelizmente, essa estratégia não funciona para os parâmetros de descanso. Eles simplesmente são transformados em matrizes:

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

Aqui, curry: ...([string, boolean[]] | [boolean[]]) => number .
Eu acho que isso poderia ser suportado se houvesse um caso especial para funções com um parâmetro de resto da tupla, onde o último elemento da tupla é uma matriz.
Nesse caso, a chamada de função permitiria que argumentos extras do tipo correto correspondessem ao array.
No entanto, isso parece muito complexo para valer a pena.

Isso tem dois problemas:

  1. O código está incorreto. curried() não aceita um array. Preencher o parâmetro c rest com [true, false] pode ser feito por curried('foo', ...[true, false]) mas isso irá falhar no TypeScript com esta sugestão. Podemos não fornecer solução de digitação para alguns casos, mas é desencorajado fornecer alguém errado!
  2. Sem querer, você combinou parâmetros opcionais e restantes e revelou um bug em sua proposta. curried() não pode ser chamado sem b mas com c . Fazer isso levará a um mau comportamento. TypeScript sabe que curried() é (...items: [string, boolean[]] | [boolean[]]) mas isso não é verdade . Como o JavaScript é menos digitado, passar [true, false] para c (assumindo que resolvemos o problema acima) com curried([true, false]) não definirá b para undefined (ou seu valor padrão) e c para [true, false] , mas definirá b para true e c para [false] !

Eu sugiro as seguintes correções:

  1. Para o segundo (e mais fácil) problema, a solução é simples: não inferir tupla de união para o último argumento opcional (ou seja, [number, string, boolean[]] | [number, boolean[]] em nosso caso) quando houver o parâmetro rest. Em vez disso, deduza [number, string, boolean[]] | [number] - ou seja, um caso para assinatura completa, incluindo todos os opcionais e restantes, um para cada opcional exceto o último e um sem o último e o resto.
  2. O primeiro problema é mais complicado: você já disse que acha que é muito complexo para valer a pena. Acho que _é_ vale a pena dada a popularidade dos parâmetros de descanso, mas é _necessário_ por causa do primeiro problema (vitória! 😄). Eu acho que seria bom se expuséssemos a interface para tuple-with-last-rest-array (penso sobre a sintaxe [t1, t2, t3, ...arr] ), mas não é necessário. Podemos ficar com ele como um interno (haha, você ainda terá que lidar com como exibir o tipo em um IDE 😈).

Mas depois de todas as reclamações e provocações, ótima proposta! Obrigado 👍 (apenas para pacificá-lo, este é o primeiro problema no GitHub ao qual respondi com três emojis - 👍, 🎉 e ❤️).

Isso seria muito útil no Injetor Angular que atualmente deve usar any https://github.com/angular/angular/issues/37264

Neste exemplo, A, B, C podem ser representados como um único tipo genérico ...A variadic. Mas não tenho ideia de como isso seria mapeado para algo onde cada elemento do genérico variadic seria incluído em outro tipo ( Type ). talvez com um tipo de ajudante? Ou a sintaxe deve 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: uma implementação de Provider injetaria instâncias de deps naquela função de fábrica nessa ordem. Uma tipificação estrita garantiria que o desenvolvedor saiba o que seria injetado e em que ordem.)

Quando isso for feito, lembre-se de atualizar o segundo parâmetro de String.prototype.replace, para que ele finalmente tenha a digitação adequada em Typescript!

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter

@Griffork Você sabe que seria necessário analisar a regex para descobrir quantos grupos de captura ela tem, certo?

Isso seria realmente útil no Injetor Angular que atualmente deve usar any angular / angular # 37264

Neste exemplo, A, B, C podem ser representados como um único tipo genérico ...A variadic. Mas não tenho ideia de como isso seria mapeado para algo onde cada elemento do genérico variadic seria incluído em outro tipo ( Type ). talvez com um tipo de ajudante? Ou a sintaxe deve 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: uma implementação de Provider injetaria instâncias de deps naquela função de fábrica nessa ordem. Uma tipificação estrita garantiria que o desenvolvedor saiba o que seria injetado e em que ordem.)

@AlexAegis

Eu sinto que seria digitado mais ou menos assim:

export declare interface TypedFactoryProvider<T, ...P> {
  provide: Type<T | T[]> | InjectionToken<T | T[]>;
  multi?: boolean;
  useFactory: (...providers: ...P) => T;
  deps: [...Type<P>];
}

Esse problema agora foi corrigido pelo número 39094, previsto para TS 4.0.

Se vier com 4.0, agora temos um motivo para chamá-lo de 4.0 😃
Este é realmente um novo recurso importante 🎉

Isso é ótimo! A única coisa "esquerda" é a mesma para tipos de string literais

@sandersn Estou tentando pensar em como essa sintaxe seria usada em coisas como RxJS , onde os parâmetros do método pipe são meio que dependentes um do outro,

como em pipe(map<T, V>(...), map<V, U>(...), filter(...), ...) . Como você digitaria de uma forma que não seja o que eles fazem agora? (As dezenas de linhas de tipagem de diferentes comprimentos variáveis)

@gioragutt usando o PR que @ahejlsberg enviou, acho que funcionaria, mas posso estar errado 😄

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 Não funciona muito bem , devido a um erro de tipo circular.

No entanto, a solução alternativa usual funciona .

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 Isso não parece funcionar para mim. 😢
Exemplo de parque infantil .

Tenho algo mais perto de funcionar, mas não deduz os tipos.
exemplo de playground

Eu tive que mudar
R extends readonly [unknown] ? 1 : 0
para
R extends readonly [infer _, ...infer __] ? 1 : 0

Não sei porque

@tylorr @treybr Budap Pode estar relacionado: https://github.com/microsoft/TypeScript/pull/39094#issuecomment -645730082

Além disso, em ambos os casos, eu recomendo compartilhar isso na solicitação de pull em que o comentário está.

Os tipos de tupla variável são uma adição incrível à linguagem, obrigado pelo esforço!

Ao que parece, construções como curry também podem se beneficiar (apenas testado com o playground de teste ):

// 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

A função genérica não funciona com o curry acima.

Não acredito que esse PR resolva esse problema em particular tbh.

Com o PR isso funciona

function foo<T extends any[]>(a: [...T]) {
  console.log(a)
}

foo<[number, string]>([12, '13']);

Mas este problema gostaria de ver uma implementação para isso, tanto quanto eu vejo:

function bar<...T>(...b: ...T) {
  console.log(b)
}

bar<number, string>(12, '13');

Há muitos colchetes angulares lá, parece um pouco redundante.

@AlexAegis Não tenho certeza se vejo muito valor em "parâmetros de tipo de descanso" como esse. Você já pode fazer isso:

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

Não pense que realmente queremos um conceito totalmente novo (ou seja, parâmetros de tipo de repouso) apenas para que os colchetes possam ser evitados nos raros casos em que a inferência não consegue descobri-lo.

@ahejlsberg entendo. Eu estava perguntando porque algumas bibliotecas (RxJS conforme mencionado) usavam soluções alternativas para fornecer essa funcionalidade. Mas é finito.

bar<T1>(t1: T1);
bar<T1, T2>(t1: T1, t2:T2);
bar<T1, T2, T3>(t1: T1, t2:T2, t3: T3, ...t: unknown) { ... }

Então, agora eles ficam com isso ou fazem os usuários digitarem os colchetes, o que é uma alteração importante e não tão intuitiva.

A razão pela qual usei este exemplo é porque aqui é simples definir o tipo dessa tupla. Um colchete aqui, um ali

foo<[number, string]>([12, '13']);

Aqui não é tão óbvio que a tupla se refere a esse parâmetro resto se você olhar de fora

foo<[number, string]>(12, '13'); 

Mas sim, como você disse, se deixarmos a inferência descobrir isso, esses casos triviais não exigirão nenhuma modificação por parte do usuário. Mas não sabemos se eles os definiram explicitamente ou não, cabe a eles, então ainda conta como uma alteração significativa. Mas essa é a preocupação da lib e não dessa mudança.

Dito isso, acho estranho que, se houver parâmetros de descanso, definidos de fora um por um, que são uma única matriz por dentro diferenciada por ... , não possam ser genéricos da mesma maneira: um por um por fora, array único por dentro, diferenciado por ... .

Discrepâncias de sintaxe menores não valem o custo de suporte para um
Gentil. Usar tipos seria uma decisão de design correta quando o TS estava planejando
suporte para parâmetros de descanso, mas acho que agora pode levar a mais
confusão tanto para desenvolvedores de linguagem quanto para usuários. Precisávamos de uma solução para
este problema, e Anders fez seu trabalho excepcionalmente bem evitando que
complexidade aderindo a [...T] vez de T . Tirem o chapéu!

(Poderíamos agora dar uma olhada em um bug que unifica o tipo de interseção para
a variável inferida no tipo condicional retorna o tipo de interseção mais à direita
argumento, ou que a união de matrizes não é matriz de união, por favor? Nós ainda
têm grandes obstáculos no sistema de tipos.)

Na sexta-feira, 19 de junho de 2020, 10:41 Győri Sándor [email protected] escreveu:

@ahejlsberg https://github.com/ahejlsberg Entendo. Eu estava perguntando porque
algumas bibliotecas (RxJS como mencionado) usaram soluções alternativas para fornecer este
funcionalidade. Mas é finito.

Barra(t1: T1); bar(t1: T1, t2: T2); bar(t1: T1, t2: T2, t3: T3, ... t: desconhecido) {...}

Então agora eles ficam com isso, ou fazem os usuários digitarem os colchetes,
o que não é tão intuitivo.

A razão pela qual usei este exemplo é porque aqui é simples
que eu defini o tipo dessa tupla. Um colchete aqui, um ali

foo <[número, string]> ([12, '13']);

Aqui não é tão óbvio que a tupla se refere a esse parâmetro restante se
você olha de fora

foo <[número, string]> (12, '13');

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646490130 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AAWYQIMTTB6JEPSQFUMTMDTRXMJD5ANCNFSM4BTBQ7DQ
.

É claro que não estou nem perto do calibre dele, mas discordo respeitosamente de @ahejlsberg .
Em minha experiência, grande parte da complexidade do texto datilografado vem do fato de que muitos recursos (interessantes e úteis, com certeza) são incluídos em seus próprios conceitos.

Essa complexidade não é inerentemente uma função do número de recursos!
Em vez disso, a linguagem poderia ser projetada em torno de conceitos maiores e mais abrangentes a partir dos quais esses casos especiais poderiam ser deduzidos trivialmente ou implementados na biblioteca std (tipo).

O mais geral tal conceito seria, obviamente, implementar totalmente tipos dependentes, a partir dos quais todo o resto poderia ser derivado, mas ir tão longe não é necessário:
Como o C ++ e, em menor medida, o Rust mostraram, alguns conceitos consistentes em grande escala fornecem uma tonelada de recursos gratuitamente.
Isso é semelhante ao que OCaml e Haskell (e presumo que F #?) Fizeram no nível de valor, apenas no nível de tipo.

A programação em nível de tipo não é nada para se temer, contanto que seja projetada na linguagem em vez de incorporada para fornecer recursos específicos.
As facilidades em C ++ 14/17 são muito intuitivas, exceto por sua sintaxe, que é puramente devido à bagagem histórica.

Conceitos abrangentes poderiam ter sido adicionados ao design original. Depois do design
erro já foi cometido, a consistência não pode ser adicionada sem arriscar enorme
incompatibilidade de volta. Eu concordo com as suspeitas em relação ao design de linguagem como
um todo (TS está muito longe dos padrões estabelecidos pela academia, ninguém pode
discordo disso). Existem muitos bugs e inconsistências que são
fundamental para milhões de bases de código de produção. Mero fato que
os desenvolvedores podem fazer acréscimos úteis à linguagem
sem corrigir acidentalmente esses bugs é, na minha humilde opinião, incrível
e merece respeito. TS tem as mesmas complexidades de design que C ++ aqui, mas seu
o sistema de tipos expressivos piora a situação.

Na sexta-feira, 19 de junho de 2020, 12h47, Bennett Piater [email protected] escreveu:

É claro que não estou nem perto do calibre dele, mas discordo respeitosamente
com @ahejlsberg https://github.com/ahejlsberg .
Na minha experiência, grande parte da complexidade do texto datilografado vem dofato de que muitos recursos (interessantes e úteis com certeza) sãoespecial-case em como seus próprios conceitos.

Essa complexidade não é inerentemente uma função do número de recursos
no entanto!
Em vez disso, a linguagem poderia ser projetada em torno de maiores, mais abrangentes
conceitos a partir dos quais esses casos especiais poderiam ser deduzidos trivialmente, ou
implementado na biblioteca std (tipo).

O mais geral tal conceito seria, naturalmente, implementar totalmente
tipos dependentes, dos quais tudo o mais poderia ser derivado, mas
ir tão longe não é necessário:
Como C ++ e, em menor medida, Rust mostraram, alguns em grande escala,
conceitos consistentes fornecem uma tonelada de recursos gratuitamente.
Isso é semelhante ao que OCaml e Haskell (e presumo que F #?) Fizeram em
o nível de valor, apenas no nível de tipo.

A programação de nível de tipo não é nada para se temer, contanto que seja
projetado para o idioma em vez de anexado para fornecer
recursos.
As facilidades em C ++ 14/17 são muito intuitivas, exceto por sua sintaxe,
que é puramente devido à bagagem histórica.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646543896 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AAWYQIMWYLGGCWPTDBZJR4TRXMX4RANCNFSM4BTBQ7DQ
.

@ polkovnikov-ph Fico feliz por termos concordado com o problema em questão :)

Quanto à solução, acho que ainda valeria a pena considerar uma mudança progressiva em direção a um sistema de tipos projetado com mais cuidado. Afinal de contas, as versões principais são uma coisa, e a alternativa é acabar no cluster * * que é C ++ 20 - uma tentativa admirável de adicionar recursos ainda mais bem projetados em cima de 2 camadas de tentativas anteriores que não podem ser removidas, em um sintaxe que já não é analisável de forma determinística.

Tudo isso está fora do tópico deste tópico e está sendo discutido aqui . Vou tentar ser franco:

Demorou décadas para a academia descobrir a abordagem correta para a subtipagem: o sistema de tipos mlsub foi criado apenas 6 anos atrás, bem depois que o TypeScript foi lançado pela primeira vez. Pode ser essa base para classes, interfaces, tipos de união e interseção com conceitos abrangentes.

Mas lembre-se também de que existem tipos condicionais. Não conheço nenhum artigo que lhes dê semântica formal ou que descreva um sistema de tipo mínimo com tipos condicionais com provas de progresso / preservação. Eu acredito que isso pode ter algo a ver com os cientistas ainda serem tímidos para publicar suas tentativas fracassadas. Se a sua proposta assume que as principais versões incompatíveis serão feitas em 2040, quando a academia se familiarizar com os tipos condicionais, posso concordar.

Caso contrário, o "sistema de tipos cuidadosamente projetado" teria que remover os tipos condicionais da linguagem, e não acho que alguém tenha a tarefa de converter 60% do DefinitelyTyped para usar qualquer alternativa escolhida para substituí-los. (E depois faça isso várias vezes, porque não é o único problema).

Receio que a única solução viável é criar uma linguagem de programação separada que de alguma forma se pareça com o TS e de alguma forma (não apenas por ser mais prazeroso escrever código) atrair os desenvolvedores a usá-la. Ryan foi bastante vocal recomendando esta abordagem para melhoria de TS anteriormente.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

blendsdk picture blendsdk  ·  3Comentários

zhuravlikjb picture zhuravlikjb  ·  3Comentários

wmaurer picture wmaurer  ·  3Comentários

CyrusNajmabadi picture CyrusNajmabadi  ·  3Comentários

siddjain picture siddjain  ·  3Comentários