Typescript: Vorschlag: Variadic Kinds -- Geben Sie den Variadic-Funktionen spezifische Typen

Erstellt am 29. Okt. 2015  ·  265Kommentare  ·  Quelle: microsoft/TypeScript

Variadische Arten

Geben Sie den Variadic-Funktionen spezifische Typen

Dieser Vorschlag ermöglicht es Typescript, Funktionen höherer Ordnung Typen zuzuweisen, die eine variable Anzahl von Parametern annehmen.
Zu solchen Funktionen gehören concat , apply , curry , compose und fast jeder Dekorateur, der eine Funktion umschließt.
In Javascript wird von diesen Funktionen höherer Ordnung erwartet, dass sie variadische Funktionen als Argumente akzeptieren.
Mit den Standards ES2015 und ES2017 wird diese Verwendung noch häufiger, da Programmierer beginnen, Spread-Argumente und Rest-Parameter sowohl für Arrays als auch für Objekte zu verwenden.
Dieser Vorschlag befasst sich mit diesen Anwendungsfällen mit einer einzigen, sehr allgemeinen Typisierungsstrategie, die auf Arten höherer Ordnung basiert.

Dieser Vorschlag würde mehrere Probleme ganz oder teilweise angehen, darunter:

  1. #5331 -- Tupel als Typen für Ruhe ... Argumente
  2. #4130 -- Compiler meldet fälschlicherweise nicht übereinstimmende Parameter-/Aufrufzielsignaturen, wenn der Spread-Operator verwendet wird
  3. #4988 -- Tupel sollten mit Array.prototype.slice() klonbar sein
  4. #1773 -- Variadische Generika?
  5. #3870 -- Resttypen in Generika für Kreuzungstypen.
  6. #212 -- bind, call und apply sind nicht typisiert (erfordert die this-Funktionstypen von #3694).
  7. #1024 -- Typisierte ...rest-Parameter mit Generika

Ich werde diesen Vorschlag auf meinem Fork des Typescript-Handbook aktualisieren: sandersn/ TypeScript-Handbook@76f5a75868de3fb1ad4dbed5db437a8ab61a2698
Ich habe eine laufende Implementierung unter sandersn/ TypeScript@f3c327aef22f6251532309ba046874133c32f4c7, in der derzeit die einfachen Teile des Vorschlags implementiert sind.
Es ersetzt Teil 2 meines vorherigen Vorschlags, #5296.
Bearbeiten: Abschnitt zur Zuweisbarkeit hinzugefügt. Ich bin mir nicht mehr sicher, ob es #5296 strikt übertrifft.

Vorschaubeispiel mit curry

curry für Funktionen mit zwei Argumenten ist einfach in Javascript und Typescript zu schreiben:

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

und in Typescript mit Typenanmerkungen:

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

Eine Variadic-Version ist jedoch in Javascript leicht zu schreiben, kann jedoch in TypeScript nicht mit einem Typ versehen werden:

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

Hier ist ein Beispiel für die Verwendung von Variadic-Arten aus diesem Vorschlag, um curry einzugeben:

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

Die Syntax für variadische Tupeltypen, die ich hier verwende, stimmt mit der Spread- und Rest-Syntax überein, die für Werte in Javascript verwendet wird.
Dies ist leichter zu erlernen, kann jedoch die Unterscheidung zwischen Typanmerkungen und Wertausdrücken erschweren.
In ähnlicher Weise sieht die Syntax zum Verketten wie Tupelkonstruktion aus, obwohl es sich in Wirklichkeit um eine Verkettung von zwei Tupeltypen handelt.

Sehen wir uns nun einen Beispielaufruf an 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')();

Beim ersten Anruf,

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

Beim zweiten Anruf

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

Syntax

Die Syntax einer Variadic-Art-Variablen lautet ...T wobei _T_ ein Bezeichner ist, der per Konvention ein einzelner Großbuchstabe ist, oder T gefolgt von einem PascalCase Bezeichner.
Variadische Artvariablen können in einer Reihe von syntaktischen Kontexten verwendet werden:

Variadische Arten können an die übliche Position für die Typparameterbindung gebunden werden, einschließlich Funktionen und Klassen:

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

Und sie können an jedem Ort der Typannotation referenziert werden:

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

Variadische Typvariablen sind wie Typvariablen ziemlich undurchsichtig.
Sie haben im Gegensatz zu Typvariablen eine Operation.
Sie können mit anderen Arten oder mit tatsächlichen Tupeln verkettet werden.
Die dafür verwendete Syntax ist identisch mit der Tuple-Spreading-Syntax, jedoch an der Position der Typannotation:

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

Tupeltypen sind Instanzen von Variadic-Arten, sodass sie weiterhin überall dort erscheinen, wo bisher Typanmerkungen zulässig waren:

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

Semantik

Eine Variadic-Art-Variable repräsentiert einen Tupeltyp beliebiger Länge.
Da es eine Menge von Typen darstellt, verwenden wir den Begriff 'Art', um ihn zu bezeichnen, in Anlehnung an seine Verwendung in der Typentheorie.
Da die Menge von Typen, die es repräsentiert, aus Tupeln beliebiger Länge besteht, qualifizieren wir 'art' mit 'variadic'.

Daher ermöglicht die Deklaration einer Variablen vom variadischen Tupeltyp, dass sie jeden _single_ Tupeltyp annehmen kann.
Wie Typvariablen können auch Typvariablen nur als Parameter für Funktionen, Klassen usw. deklariert werden, wodurch sie dann innerhalb des Rumpfes verwendet werden können:

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

Das Aufrufen einer Funktion mit Argumenten, die als variadische Art typisiert sind, weist der Art einen bestimmten Tupeltyp zu:

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

Weist den Tupeltyp ...T=[number,number,string] ...T . So in this application of f , let a:...T is instantiated as let a:[Zahl,Zahl,Zeichenfolge] zu . 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` ist erlaubt.
Beispielsweise können dem Tupel neue Elemente hinzugefügt werden:

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

Wie Typvariablen können variadische Typvariablen normalerweise abgeleitet werden.
Die Aufrufe an cons hätten mit Anmerkungen versehen werden können:

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

cons muss beispielsweise zwei Variablen ableiten, einen Typ _H_ und einen Typ _...Tail_.
Im innersten Aufruf cons("foo", ["baz", false]) , H=string und ...Tail=[string,boolean] .
Im äußersten Aufruf H=number und ...Tail=[string, string, boolean] .
Die _...Tail_ zugewiesenen Typen erhält man, indem man Listenliterale als Tupel eingibt -- es können auch Variablen eines Tupeltyps verwendet werden:

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

Darüber hinaus können variadische Artvariablen abgeleitet werden, wenn sie mit Typen verkettet werden:

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

Hier wird der Typ von l als [number, string, boolean] .
Dann H=number und ...Tail=[string, boolean] .

Grenzen der Typinferenz

Verkettete Arten können nicht abgeleitet werden, da der Prüfer nicht erraten kann, wo die Grenze zwischen zwei Arten liegen sollte:

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

Der Prüfer kann nicht entscheiden, ob er zuordnen soll

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

Einige eindeutige Anrufe sind ein Opfer dieser Einschränkung:

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

Die Lösung besteht darin, Typanmerkungen hinzuzufügen:

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

Es können nicht überprüfbare Abhängigkeiten zwischen Typargumenten und dem Funktionsrumpf entstehen, wie in 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);

Diese Funktion kann typisiert werden, aber es besteht eine Abhängigkeit zwischen n und den kind-Variablen: n === ...T.length muss wahr sein, damit der Typ korrekt ist.
Ich bin mir nicht sicher, ob dies tatsächlich zulässiger Code ist.

Semantik auf Klassen und Interfaces

Die Semantik ist bei Klassen und Interfaces gleich.

TODO: Es gibt wahrscheinlich einige klassenspezifische Falten in der Semantik.

Zuweisbarkeit zwischen Tupeln und Parameterlisten

Tupelarten können verwendet werden, um Restargumenten von Funktionen innerhalb ihres Gültigkeitsbereichs einen Typ zuzuweisen:

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

In diesem Beispiel muss die Parameterliste von f: (a: number, b:string) => string dem für die Art ...T instanziierten Tupeltyp zuweisbar sein.
Der abgeleitete Tupeltyp ist [number, string] , was bedeutet, dass (a: number, b: string) => string (...args: [number, string]) => string zuweisbar sein muss.

Als Nebeneffekt können Funktionsaufrufe diese Zuweisbarkeit nutzen, indem sie Tupel in Restparameter verteilen, auch wenn die Funktion keine Tupelart hat:

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

Generierte Tupeltypen für optionale und Restparameter

Da Tupel optionale Parameter nicht direkt darstellen können, ist der generierte Tupeltyp eine Vereinigung von Tupeltypen, wenn eine Funktion einem Funktionsparameter zugewiesen wird, der von einer Tupelart typisiert ist.
Schauen Sie sich die Art von h nachdem sie gecurry wurde:

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

Hier ...T=([number] | [number, string]) , also curried: ...([number] | [number, string]) => number die wie erwartet aufgerufen werden können. Leider funktioniert diese Strategie nicht für Ruheparameter. Diese werden einfach in Arrays umgewandelt:

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

Hier, curried: ...([string, boolean[]] | [boolean[]]) => number .
Ich denke, dies könnte unterstützt werden, wenn es einen Sonderfall für Funktionen mit einem Tupelrestparameter gäbe, bei dem das letzte Element des Tupels ein Array ist.
In diesem Fall würde der Funktionsaufruf zusätzliche Argumente des richtigen Typs zulassen, um dem Array zu entsprechen.
Das scheint jedoch zu komplex, um sich zu lohnen.

Erweiterungen zu den anderen Teilen des Typoskripts

  1. Typescript erlaubt es Benutzern nicht, einen leeren Tupeltyp zu schreiben.
    Dieser Vorschlag erfordert jedoch, dass variadische Arten an ein leeres Tupel gebunden werden können.
    Typescript muss also leere Tupel unterstützen, wenn auch nur intern.

    Beispiele

Die meisten dieser Beispiele sind im aktuellen Typescript als Funktionen mit festem Argument möglich, aber mit diesem Vorschlag können sie als variadisch geschrieben werden.
Einige, wie cons und concat , können im aktuellen Typescript für homogene Arrays geschrieben werden, können aber jetzt für heterogene Tupel mit Tupelarten geschrieben werden.
Dies folgt der typischen Javascript-Praxis genauer.

Einen verketteten Typ zurückgeben

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]

Verketteter Typ als Parameter

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"

Variadische Funktionen als Argumente

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: Könnte f ...U anstelle von U ?

Dekorateure

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

Offene Fragen

  1. Hält die Geschichte der Zuweisung von Tupel-zu-Parameter-Listen stand? Es ist besonders wackelig bei optionalen und Ruheparametern.
  2. Wird der abgeleitete Typ eine Vereinigung von Tupeln sein, wie im Fall des optionalen Parameters? Da bind , call und apply Methoden sind, die in Function definiert sind, müssen ihre Typargumente zum Zeitpunkt der Funktionserstellung gebunden werden und nicht an die bind Aufrufseite (zum Beispiel). Dies bedeutet jedoch, dass Funktionen mit Überladungen keine für ihre Argumente spezifischen Typen annehmen oder zurückgeben können – sie müssen eine Vereinigung der Überladungstypen sein. Außerdem verfügt Function über keinen Konstruktor, der Typargumente direkt angibt, sodass es wirklich keine Möglichkeit gibt, bind et al. die richtigen Typen bereitzustellen. TODO: Fügen Sie hier ein Beispiel hinzu. Beachten Sie, dass dieses Problem nicht unbedingt nur für variadische Funktionen gilt.
  3. Sollten Restparameter in Sonderbuchstaben geschrieben werden, um ihre schöne Aufrufsyntax beizubehalten, selbst wenn sie aus einem Tupeltyp generiert werden? (In diesem Vorschlag müssen Funktionen, die durch eine Tupelart typisiert werden, Arrays an ihre Restparameter übergeben, sie können keine zusätzlichen Parameter haben.)
Fix Available In Discussion Suggestion

Hilfreichster Kommentar

Dieses Problem wird jetzt von #39094 behoben, das für TS 4.0 vorgesehen ist.

Alle 265 Kommentare

+1, das ist wirklich nützlich für die funktionale Programmierung in TypeScript! Wie würde das mit optionalen oder Rest-Argumenten funktionieren? Kann die Funktion compose konkreter für Funktionen mit Restargumenten oder optionalen Argumenten verwendet werden?

Guter Punkt. Ich denke, Sie könnten einer optional-param-Funktion den kleinsten zulässigen Tupeltyp zuweisen, da Tupel nur Objekte sind, die zusätzliche Member zulassen. Aber das ist nicht ideal. Ich werde sehen, ob ich das Beispiel für compose herausfinden kann, und dann werde ich den Vorschlag aktualisieren.

Tatsächlich würden Gewerkschaftstypen wahrscheinlich besser funktionieren. Etwas wie

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

Dies bricht jedoch immer noch die Ruheparameter.

@ahejlsberg , du Tupelarten funktionieren würden, denke ich.

Also :+1: dazu. Zur Information, dies bezieht sich auf (und würde) #3870 erfüllen. Wir haben versucht, eine API für den , müssen jedoch einige der in diesem Vorschlag genannten Einschränkungen umgehen. Dies würde sicherlich einige dieser Probleme lösen!

Es scheint jedoch, dass Sie manchmal solche Tupeltypen "zusammenführen" möchten, anstatt sie beizubehalten, insbesondere bei etwas wie Compose. Zum Beispiel:

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

Außerdem haben Sie in vielen Ihrer Beispiele Primitive verwendet. Wie würden Sie etwas Komplexeres funktionieren sehen, insbesondere wenn es Konflikte gibt?

Leider bezieht sich dieser Vorschlag nicht auf #3870 oder die Typkomposition, da der einzige Kompositionsoperator für Tupelarten [T,...U] . Sie könnten dies auch als T + ...U schreiben (was eher darauf hindeutet, was mit den Typen passiert), aber #3870 und Ihre Typkompositionsbibliothek benötigen T & ...U . Ich denke, das könnte möglich sein, aber ich muss zuerst die Ideen von @JsonFreeman und @jbondc aus #3870 verstehen. Ich werde den Vorschlag erweitern, wenn ich herausfinden kann, wie es funktionieren sollte.

Hinweis: Ich habe mich für die Syntax [...T, ...U] da sie wie die äquivalente Syntax zur Verbreitung von Werten aussieht, aber T + ...U ist eher ein Hinweis darauf, was mit den Typen passiert. Wenn wir beides haben, könnten + und & die zu verwendenden Operatoren sein.

Big :+1: dazu!

+1 super! Es würde erlauben, solche Dinge viel ausdrucksvoller und leichter auszudrücken.

Mein Punkt in #3870 scheint hier ein Problem zu sein. Insbesondere mache ich mir Sorgen über das Ableiten von Typargumenten für variadische Typparameter.

Die Inferenz von Typargumenten ist ein ziemlich komplizierter Prozess, der sich im Laufe der Zeit auf subtile Weise verändert hat. Wenn Argumente mit Parametern abgeglichen werden, um Typargumente abzuleiten, gibt es keine Garantien hinsichtlich der Reihenfolge, in der Kandidaten abgeleitet werden, oder wie viele Kandidaten (für einen bestimmten Typparameter) abgeleitet werden. Dies war im Allgemeinen kein Problem, da das dem Benutzer angezeigte Ergebnis (in den meisten Fällen) diese Details nicht offenlegt. Aber wenn Sie aus den Inferenzergebnissen einen Tupeltyp machen, legt dies sicherlich sowohl die Reihenfolge als auch die Anzahl der Inferenzen offen. Diese Details sollten nicht beobachtbar sein.

Wie ernst ist das? Ich denke, es hängt davon ab, wie genau die Inferenz funktioniert. Was ist das Ergebnis des Folgenden:

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

@jbondc , - scheint eine gute Idee zu sein. Ich werde es im Hinterkopf behalten, aber hier nicht näher darauf eingehen, da wir meiner Meinung nach neue Typoperatoren nacheinander einführen sollten. Sowohl & als auch + erzeugen neue Typen, aber & erzeugt einen Schnittpunkttyp, während + einen neuen Tupeltyp erzeugt (deshalb bevorzuge ich die Syntax [T,...U] statt T + ...U , weil [T,U] bereits für Typen macht).

@JsonFreeman Ich denke, es ist in Ordnung, eines von zwei Dingen mit wiederholten Art-Parametern zu tun:

  1. Verbinden Sie die Typen: f(['hello', 1], [1, false]): [string | number, number | boolean]
  2. Verbieten Sie die Inferenz von wiederholten Tupeltypparametern, insbesondere wenn sich die Inferenz von Typargumenten als kompliziert erweist. Etwas wie das:
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

Ich denke, dass echte Bibliotheken (wie die reaktiven Erweiterungen, mit denen @Igorbek verlinkt ist) normalerweise nur einen

In den obigen Beispielen, curry ist die am schwierigsten zu schließen - Sie überspringen haben f: (...args:[...T,...U]) => V , infer ...ts:...T , dann gehen Sie zurück und setzen ...U zu dem, was übrig, nachdem ...T aus den Parametern von f verbraucht wurde.

Ich habe mit dem Prototyping begonnen (sandersn/TypeScript@1d5725d), bin aber noch nicht so weit gekommen. Irgendeine Idee, ob das geht?

Ich würde mich irren, alles zu verbieten, bei dem die Semantik nicht klar ist (wie wiederholte Schlussfolgerungen auf denselben Parameter des gespreizten Typs). Das zerstreut auch meine Bedenken oben.

Ich kann mir keinen guten Mechanismus vorstellen, um Curry zu tippen. Wie Sie betonen, müssen Sie die Parameterliste der ersten Funktion überspringen, um das Argument ...T und dann zu sehen, was übrig bleibt. Es müsste eine Richtlinie geben, um Rückschlüsse auf einen verteilten Typparameter zu verschieben, wenn er in seiner Liste nicht endgültig ist. Es könnte unordentlich werden.

Wie gesagt, ich denke, das ist einen Versuch wert. Die Nachfrage nach der Funktion ist groß.

Ich denke, Sie müssten mehrere Tupelarten überspringen, die im selben Kontext vorkommen (zB Top-Level wie (...T,string,...U) => V oder verkettet wie [...T,...U,...T] ). Dann können Sie die übersprungenen Arten mehrfach durchgehen, um bereits abgeleitete Arten zu eliminieren und noch mehrdeutige Arten erneut zu überspringen. Wenn zu irgendeinem Zeitpunkt keine einzige Art für die Inferenz verfügbar ist, stoppen Sie und geben Sie einen Fehler zurück.

Also, ja. Kompliziert.

Vielleicht können Sie sich von einem ähnlichen Problem inspirieren lassen. Es ist tatsächlich dem Problem des Ableitens auf eine Vereinigung oder Schnittmenge ziemlich ähnlich. Beim Ableiten auf einen Union-Typ, der einen Typparameter enthält, der ein Mitglied des Inferenzkontexts ist, wie in function f<T>(x: T | string[]) , wissen Sie nicht, ob auf T geschlossen werden soll. Die beabsichtigte Manifestation des Unionstyps war möglicherweise string[] . Typoskript schließt also zuerst auf alle anderen Bestandteile und dann, wenn keine Rückschlüsse gezogen wurden, auf T.

Bei Schnittmengen ist dies noch schwieriger, da Sie den Typ des Arguments möglicherweise auf die verschiedenen Schnittmengenbestandteile aufteilen müssen. Typescript zieht überhaupt keine Rückschlüsse auf Kreuzungstypen.

Was wäre, wenn Sie nur das Spreizen von Tupeln erlauben würden, wenn es der letzte Typ in seiner Sequenz ist? Also wäre [string, ...T] erlaubt, [...T, string] aber nicht?

Wenn ich das richtig verstehe, würde dies die Mixin-Story in TypeScript tatsächlich lösen. Liege ich mit diesem Verständnis richtig?

Vielleicht. Kannst du ein Beispiel geben? Ich bin nicht fließend mit Mixin-Mustern.

Die Syntax einer Variable vom Typ variadic ist ...T, wobei T ein Bezeichner ist, der per Konvention ein einzelner Großbuchstabe ist, oder T, gefolgt von einem PascalCase-Bezeichner.

Können wir den Fall eines Typparameterbezeichners dem Entwickler überlassen?

@aleksey-bykov +1. Ich sehe keinen Grund, warum das nicht der Fall sein sollte.

Entwickler mit Haskell-Hintergrund würden das zu schätzen wissen.

Entschuldigung, dieser Satz kann mehrdeutig geparst werden. Ich meinte 'oder', um genau zu analysieren: "nach Konvention (ein einzelner Großbuchstabe || T gefolgt von einem PascalCase-Bezeichner)". Ich schlage nicht vor, die Groß-/Kleinschreibung der Bezeichner einzuschränken, sondern nur auf die Konvention hinzuweisen.

Aber was es wert ist, _ich_ habe einen Haskell-Hintergrund und ich mag es nicht, Konventionen der Sprache zu brechen, in der ich schreibe.

Entschuldigung für die Entgleisung. Meine letzte neugierige Frage (wenn es Ihnen nichts ausmacht, dass ich frage): Was ist die "Konvention" von TypeScript, die möglicherweise gebrochen wird und wer ist betroffen?

@sandersn

Dies sollte check eingeben, vorausgesetzt, T & ...U bedeutet T & U & V & ... (was das intuitive Verhalten ist).

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

Oder in einer Definitionsdatei:

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

@aleksey-bykov Die Konvention, von der ich spreche, ist der Fall von Typparameterbezeichnern. Wer ist betroffen? Leute, die neuen Typescript-Code lesen müssen, den sie noch nie zuvor gesehen haben – Konventionen helfen neuen Lesern, neuen Code schneller zu verstehen.

@sandersn @aleksey-bykov hatte den Eindruck, dass Folgendes _syntaktisch_ ungültig wäre:

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

@isiahmeadows & und | Operationen über Arten werden in diesem Vorschlag nicht behandelt, obwohl ich sie zu offenen Fragen / zukünftigen Arbeiten hinzufügen sollte, wenn ich dies nicht getan habe. Im Moment ist der einzige vorgeschlagene Operator die Verkettung: [THead, ...TTail] .

Ein Unterschied besteht darin, dass die Verkettung immer noch einen Tupeltyp erzeugt, während & und | Schnitt- bzw. Vereinigungstypen erzeugen.

@sandersn Mein assign Beispiel in TypeScript wäre trivial damit zu ändern.

Obwohl:

  1. Schnittmenge würde der Verkettung ähnlich sein, obwohl es eher der Verkettung von Wörterbüchern als der Verkettung von Listen entspricht. Variadic-Typen können dort zusätzlich zu bestehenden Maschinen implementiert werden.
  2. Union wäre wie eine Kreuzung, außer dass sie nur die gemeinsamen Teile behält. Auch hier könnten variadische Typen auf bestehenden Maschinen implementiert werden.

@isiahmeadows Eine Kreuzung ist im Allgemeinen keine Aneinanderreihung von Wörterbüchern. Das gilt nur für eine Schnittmenge von Objekttypen, nicht aber beispielsweise für eine Schnittmenge von Vereinigungen. Unionen sind auch nicht dasselbe wie nur die Eigenschaften, die Objekte gemeinsam haben. Die beiden sind besser charakterisiert durch die Werte, die sie bewohnen.

@sandersn Ich bin etwas verwirrt über die Inferenz von Typargumenten mit variadischen Arten. Was ist hier abzuleiten?

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

Ist das Ergebnis [string, number, number[]] ? Das würde bedeuten, dass Sie sich auf die Typargument-Inferenz verlassen müssen, die Kandidaten in einer Reihenfolge von links nach rechts hinzufügt, was keine triviale Annahme ist. Es wäre auch das erste Mal, dass das Typsystem dem Benutzer die Liste der Inferenzkandidaten anzeigt.

Ich weiß, dass dies ein experimenteller / früher Vorschlag ist, aber wir könnten die ...T Syntax für Restparameter diskutieren. Aus meiner Sicht funktioniert es nicht wirklich.
Die vorgeschlagene Syntax lautet also:

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

Vergleichen wir mit der bestehenden Syntax von Restparametern:

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

Der Typ des Parameters a , der Restargumente abfängt, ist also number[] , sodass wir klar verstehen können, dass es sich um ein Array handelt. Analog kann ich folgern, dass ...T aus dem Vorschlag auch ein Array darstellt. Aber das ist nicht sehr offensichtlich.
Nehmen wir als Nächstes an, wir könnten restriktivere Ruheparameter definieren:

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

Jetzt sehen wir also immer noch, dass der Typ von a ein Tupel ist (was ein Array ist).

Mein Vorschlag ist, den Begriff ...T konsistenter zu behandeln, um ihn als "eine abstrakte geordnete Liste von Typen" darzustellen. Und verwenden Sie es auf die gleiche Weise, wie wir den Spread-Operator verwenden:

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

Also ist ...a im Fall einer Variablen nur 1, "1" .

Meine Syntax zum Definieren von Ruheparametern durch den Begriff ...T :

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

Für mich macht es viel mehr Sinn.

@Igorbek Ich bin davon ausgegangen, dass declare function f<...T>(...a: ...T); bereits so funktioniert hat. Aber ich sehe nicht, dass declare function f(...a: [number, string]); viel genutzt wird.

Um klarer zu sein.

Ursprünglich vorgeschlagene Syntax für Ruheparameter:

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

Wenn ich das kann

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

dann schaffe ich das:

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

Der Typ von a ist also [...T] (wir geben so zurück), aber wir haben ihn in der Signatur als ...T definiert.
Wir könnten sagen, dass ...T und [...T] sind, aber es funktioniert nicht im Fall von Variablen.
Für Variablen:

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

Wenn wir dasselbe auf Standardruheparameter anwenden

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

der Typ von a ist number[] (nach Rückgabetyp), genauso wie er in der Signatur definiert wurde.

@isiahmeadows ja, function f(...a: [number, string]) funktioniert nicht. Ich habe mir gerade Gedanken gemacht, wie wir Ruheparameter behandeln können.

Also weiter gehen. Um Typparameter explizit zu definieren, wurde die folgende Syntax vorgeschlagen:

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

Wendet sich an:

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

Das könnte also auch funktionieren:

function g<T1, T2, T3>()

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

@JsonFreeman so funktioniert mein Prototyp, ja. Aber ich kenne den Typinferenzalgorithmus nicht genug, um zu verstehen, warum er funktioniert. Mit anderen Worten, die Frage ist nicht, ob die Schlussfolgerung von links nach rechts eine _triviale_ Annahme ist, sondern eine richtige. Für den Identitätsfall lautet die Antwort ja, aber ich weiß nicht, ob Sie Fälle konstruieren können, in denen die Antwort nein lautet.

Können Sie auch ein Beispiel für einen exponierten Satz von Typinferenzkandidaten durcharbeiten? Wie gesagt, ich verstehe die Funktionsweise des Inferenzalgorithmus nicht sehr gut, daher würde mir ein Beispiel helfen, zu verstehen, was Sie meinen.

Und noch besser:

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

Ich schlage vor, dem Typbezeichner [] voranzustellen, um den Rest der Typparameter zu kennzeichnen.

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

Es ist 1 Zeichen kürzer als ...T und macht (meiner Meinung nach) weniger visuelles Rauschen.

@aleksey-bykov Ich bin da eigentlich ganz anderer Meinung. Es passt nicht zur bestehenden Syntax der Restparameter, daher glaube ich, dass es auf einen Blick auch weniger klar ist.

[...T] / T als Rest-Array-Parametertyp scheint mir viel besser zu sein. Vergleichen Sie noch einmal mit Array und ihrem Sprad-Operator:

| Arrays | Typen (aus Vorschlag) | Typen (mein Update) |
| --- | --- | --- |
| var x = [1,2] | nein | T = [T1, T2] |
| [0, ...x] === [0,1,2] | [T0, ...T] === [T0, T1, T2] | [T0, ...T] === [T0, T1, T2] |
| f(x) === f([1, 2]) | nein | 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> |

Aus Vorschlag

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
}

Von meinem Update

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

Das Update sieht jetzt IMO schöner aus. Listen zur Darstellung von Typen klingen sehr nett, aber selbst getippte Lisps gehen nicht so weit (homoikonische Typen, irgendjemand? :smile:).

Ich bekomme den Reiz der Reinheit, aber ich betrachte auch den pragmatischen Aspekt. Listen wären auch alleine relativ einfach zu implementieren, passen aber nicht zum Rest der Sprache. Es ist fast wie bei den zahlreichen Versuchen, Monaden in Java (der Sprache) oder Lambdas in C zu implementieren - sie fallen immer unglaublich hässlich und hackig aus.

@sandersn Ich kann versuchen zu erklären, was ich meine, indem ich die Kandidatenliste

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

Die Argumente werden typisiert und dann auf jeden Parameter abgeleitet. Es werden zwei Kandidaten generiert, nämlich (string | number)[] und string[] . Aber der erste wird gewinnen, weil er ein Supertyp des zweiten ist. Als Ergebnis bemerkt der Benutzer nie, dass string[] jemals im Bild war. Es gibt eine Schlussfolgerung für T , und alle anderen Kandidaten sind unsichtbar. Dies bedeutet, dass für den Benutzer zwei Dinge unsichtbar sind, nämlich die Reihenfolge der Kandidaten und die Multiplizitäten der Kandidaten.

Hier ist ein Problem mit den Multiplizitäten, wenn Sie sich auf die Kandidatenliste als Ihre Liste von Elementen im Tupel verlassen, das mit ...T :

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

Ich denke, Sie würden [number, number] für T ableiten wollen, wenn Sie die Absicht Ihres Vorschlags so verstehen, wie ich ihn verstehe. Aber wegen der enthält Check-in-Zeile https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L6256 wird der number Kandidat nur einmal hinzugefügt und T wird als [number] . Dies ist das Multiplizitätsproblem, über das ich gesprochen habe.

Die Reihenfolge ist von links nach rechts. Es gibt jedoch mehrere Durchläufe, und Argumente werden erneut verarbeitet, wenn sie Funktionsausdrücke enthalten, die kontextabhängig typisiert werden. Wenn n Argumente mit kontextabhängig typisierten Funktionsausdrücken vorhanden sind, werden die Argumente n + 1 übergeben. Ein Beispiel ist Array.prototype.reduce, wo der initialValue-Parameter effektiv typisiert und vor dem Callback abgeleitet wird, obwohl er sich auf der rechten Seite befindet. Etwas wie das Folgende könnte ein Problem für den Vorschlag sein:

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

Intuitiv sollte T [(x: any) => any, number] , aber wenn Sie sich auf die Reihenfolge verlassen, in der die Kandidaten hinzugefügt werden, ist es [number, (x: any) => any] . Dies liegt daran, dass die Inferenz von Typargumenten im Allgemeinen von links nach rechts erfolgt, aber Funktionen, die kontextabhängiger Typisierung unterliegen, bis zum Ende verschoben werden.

Sowohl die Multiplizitäts- als auch die Reihenfolgeprobleme, die ich erläutert habe, sind Beispiele für das Auftauchen der Kandidatenliste. @ahejlsberg wird sicherlich auch eine gute Person sein, um danach zu fragen, und er kann tatsächlich helfen, alles, was ich gesagt habe, zu erklären, zu bestätigen oder zu widerlegen.

@JsonFreeman, warum denkst du, dass es ein Problem wäre?
Es kann implementiert werden, indem virtuell zusätzliche generische Typen für jedes Rest-Faktarargument eingeführt und auf eine Funktion mit fester Parameterlänge geschlossen werden.
Zum Beispiel,

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]

Übrigens, können wir ableiten, dass x => x vom Typ { <T>(x: T): T; } ?

@Igorbek Ich denke, Ihr Vorschlag zu Fertigungstypparametern (zumindest als Intuition, unabhängig davon, wie er implementiert wird) ist der richtige Weg. Sie könnten eine Sequenz von Typen für T ableiten, wobei jedes Element in der Sequenz einen Index und eine Liste von Kandidaten hat (dies ist eine alternative Möglichkeit, das von Ihnen erwähnte zu implementieren).

Mein Punkt war jedoch, ich glaube nicht, dass dies natürlich passieren würde, wenn Sie einfach die Inferenzkandidatenliste als abgeleitetes Tupel umfunktionieren. Es würde explizite Mechanik erfordern, um das Richtige zu tun.

Was Ihren Punkt zu { <T>(x: T): T; } , lässt sich das nicht gut auf die Eingabe von Dingen wie x => foo(x) verallgemeinern, wobei foo eine Funktion ist. Sie müssen den Typ von x um die Überladungsauflösung für foo .

Ein kleiner Schritt aus dem Kampf mit den Inferenzregeln der Typprüfung.
Ich habe einen Kommentar/Vorschlag zur Syntax. Ich denke, es gibt zwei konsistente, aber sich gegenseitig ausschließende Optionen:

1. Formale Argumente vom Ruhetyp

Wenn wir dieses Formular wählen:

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

dann sollten wir es verwenden wie

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

So werden es echte Restformaltypen sein. Sie sollten nur an der letzten Position des formalen Typparameterabschnitts verwendet werden.

2. Tupeltypisierte Restargumente

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

In diesem Fall haben wir immer eine feste Anzahl von Slots im Parameterabschnitt des formalen Typs.

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

Wir folgern, dass T ein Tupeltyp sein sollte, wenn eine der Bedingungen erfüllt ist:

  1. T wird für Ruheparameter verwendet wie (...args: T) => T
  2. T wird in Aufstrichzusammensetzungen wie [...T] oder [Zahl, ...T, Zeichenfolge] verwendet.

Daher brauchen wir im formalen Typparameterabschnitt keine Auslassungspunkte zu verwenden (wir können es sogar _syntaktisch_ ohne Typprüfer ableiten)

in diesem Fall können wir auch schreiben

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

aber es ist überflüssig.

Persönlich würde ich das spätere gerne in TypeScript implementiert sehen. @JsonFreeman , @sandersn?

@Artazor Ich denke, es

Ich denke, bei generischen Typreferenzen geht es nur darum zu entscheiden, wo und syntaktisch wie ein Rest-Typ-Parameter verwendet werden soll. Dies müsste für alle Typkonstruktoren entschieden werden, die eine Typsequenz annehmen (Tupel, Signaturen, generische Typreferenzen).

Bei generischen Signaturen ist es aufgrund der Typargument-Inferenz komplizierter. Was wäre, wenn Sie Folgendes hätten:

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

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

Was gibt foo zurück? Mein Punkt ist nur, dass die Leute erwarten, dass generische Regeln für generische Typen und generische Signaturen gleich sind, aber wenn Sie generische Typen ausdrucksvoller machen, muss die Typargument-Inferenz eine Möglichkeit haben, damit umzugehen. Dies kann nur eine Frage der formalen Identifizierung der Fälle sein, die für die Inferenz von Typargumenten schwierig sind, und der Benutzer muss in diesen Fällen explizite Typargumente übergeben.

Meiner Meinung nach ist deine Option 1 besser. Ich persönlich sehe die Verwendung von Tupeltypen als Restparameter nicht. Ich denke, ein Rest-Parameter sollte nur ein Array-Typ oder ein Rest-Typ-Parameter sein, da er eine variable Länge haben soll. Ich mag auch das Konzept, dass ein Parameter vom Typ rest eine abstrakte Folge von Typen ist, die nicht mit etwas verbunden sind, das bereits im Typsystem vorhanden ist.

Meine Philosophie bei Tupeln ist, dass sie eine Teilmenge von Array-Werten darstellen, deren Länge bekannt ist. Diese Arraywerte sind echte Laufzeitentitäten. Ich mag die Idee nicht, sie als eine Art Typsystemgerät zu verwenden, um eine abstrakte Folge von Typen darzustellen (zum Beispiel die Folge von Parametern in einer Signatur). Ob Sie jedoch einen Parameter vom Typ rest in einem Tupel verteilen dürfen, ist eine andere Geschichte.

Ich mag den Tupelvorschlag, weil er leistungsfähiger ist und mehr Anwendungsfälle löst. Es ist auch sehr intuitiv, dass ich ein Tupel als Restparameter verteilen kann, da Tupel nur Arrays sind und wenn ich eine Funktion mit einem Restparameter aufrufe kann ich das Array verteilen. Das Typsystem würde dann meinem Verständnis des Codes besser entsprechen.

@JsonFreeman in Ihrem Fall würde foo [string, number, number] da dies von ...args abgeleitet würde, der abgeleitete cb-Typ wäre (string, number, number) => void und der übergebene Rückruf würde nur das letzte Argument ignorieren, das ist sowohl in TS als auch in JS sehr verbreitet.

Ich mag die Idee nicht, sie als eine Art Typsystemgerät zu verwenden, um eine abstrakte Folge von Typen darzustellen

Genau das sind sie, JS kennt Tupel nicht, nur TS. Für TS ist ein Tupel eine Folge von Typen.

Ich mag auch den tupelbasierten Ansatz. Vor allem, wenn wir kompatible Funktionssignaturen wie diese haben könnten:

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

Letzteres können wir mit der aktuellen Syntax nicht ausdrücken, könnten es aber, wenn wir #6229 übernommen hätten.
Für mich scheint es also der richtige Weg zu sein, Tupel zu verwenden und Tupel zu vereinheitlichen, um mehr auszudrücken. Ohne ausdrucksvollere Tupel wäre es schwierig, so etwas wie [...T, ...T] da T als Tupel eine offene Länge hat.

@JsonFreeman für Ihr Beispiel hat @Pajn genau so gezeigt, wie ich das verstanden habe - es gibt keine sichtbaren Probleme beim Ableiten dieser Typen.

@JsonFreeman Ich würde besser diese Syntax verwenden

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

Hm, wahrscheinlich kann es zu Unklarheiten kommen:

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]

Ich könnte hinter die Idee kommen, einen Parameter vom Typ rest in einem Tupel zu verteilen. Aber ich bin mir nicht sicher, ob ein Parameter vom Typ rest implizit als Tupel interpretiert werden soll. Das Beispiel von Resttyp in allen

@Igorbek Sie haben Recht mit der Mehrdeutigkeit in Ihrem ersten Beispiel. Ihr drittes Beispiel ist jedoch auch problematisch. Bei einer Sequenz wie number, string gibt es 2 mögliche Instanziierungen der Signatur. Nämlich (arg1: number, arg2: string) => [number, string] sowie (arg1: [number, string]) => [number, string] (um des Beispiels willen die implizite Tupelinterpretation zu übernehmen).

Das andere Merkwürdige an der impliziten Tupelinterpretation ist folgendes: Angenommen, Sie haben einen Parameter vom Typ Pause T, der in number, string instanziiert wird. Angenommen, Sie übergeben diese als Typargumente, Foo<T> . Ist das als Foo<[number, string]> zu interpretieren, während Foo<...T> Foo<number, string> ? Dafür gibt es ein Argument, denn es würde den Spread-Operator auf das Typsystem ausdehnen. Aber ich würde immer noch lieber die Tupelversion als Foo<[...T]>

Nennen Sie mich verrückt, aber ich spüre einige grundlegende Mängel bei der Idee, zu verwenden
Tupel. Was passiert, wenn Sie versuchen, einen Tupeltyp auf zu viele zu verteilen?
Parameter? So was?

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

Was passiert auch, wenn die Typparameter vom falschen Typ sind oder in verwendet werden?
ungewöhnliche, möglicherweise fehlerhafte Orte?

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

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

Das erste Beispiel ist direkt relevant für Function#apply (und könnte ein
error), und der zweite ist ein nicht offensichtlicher Fehler, der nicht kompiliert werden kann.
und nicht trivial mit Intellisense zu erkennen.

Am Sonntag, 28. Februar 2016, 03:04 schrieb Jason Freeman [email protected] :

Das andere Merkwürdige an der impliziten Tupelinterpretation ist folgendes: sag
Sie haben einen Rest-Typ-Parameter T, der als Zahl, Zeichenfolge instanziiert wird.
Sagen Sie jetzt, Sie übergeben diese als Typargumente, Foo. Soll das sein
als Foo<[Zahl, Zeichenfolge]> interpretiert, während Foo<...T> Foo . ist Zeichenfolge>?


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189817561
.

@JsonFreeman

Ihr drittes Beispiel ist jedoch auch problematisch. Bei einer Sequenz wie number, string gibt es 2 mögliche Instanziierungen der Signatur. Nämlich (arg1: number, arg2: string) => [number, string] sowie (arg1: [number, string]) => [number, string] (um des Beispiels willen die implizite Tupelinterpretation zu übernehmen).

Aus meinem dritten Beispiel ist klar, dass es nur als (...args: [number, string]) => [number, string] interpretiert werden kann:

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

Das andere Merkwürdige an der impliziten Tupel-Interpretation ist folgendes: Angenommen, Sie haben einen Rest-Typ-Parameter T , der in number, string instanziiert wird.

T kann nicht in number, string instanziiert werden, da es sich um ein echtes Tupel handelt. Es muss [number, string] .

Angenommen, Sie übergeben diese als Typargumente, Foo<T> . Ist das als Foo<[number, string]> zu interpretieren, während Foo<...T> Foo<number, string> ?

Wahr. Allerdings scheint <...T> für diese speziellen Anwendungsfälle, die wir besprechen, überflüssig zu sein (Positionierte Typen für Rest-Argumente abfangen). Sagen wir trotzdem, wir haben es.

Dafür gibt es ein Argument, denn es würde den Spread-Operator auf das Typsystem ausdehnen. Aber ich würde immer noch lieber die Tupelversion als Foo<[...T]>

Es gibt zwei Fälle, in denen wir diese Syntax verwenden könnten:

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

Für die Verwendung ist es also nichts anderes als Typoperatoren wie | , & oder [] . Aber in der Deklaration könnte die Syntax als T extends any[] oder welcher Basistyp für alle Tupel interpretiert werden, um anzuzeigen, dass es sich um einen Tupeltyp handeln muss.

@isiahmeadows

Was passiert, wenn Sie versuchen, einen Tupeltyp auf zu viele zu verteilen?
Parameter? So was?

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

Was passiert auch, wenn die Typparameter vom falschen Typ sind oder in verwendet werden?
ungewöhnliche, möglicherweise fehlerhafte Orte?

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

Ich sehe immer noch nicht den Vorteil, diese Dinge als Tupel darzustellen. Außerdem denke ich, dass sie als <...T> und nicht als <T> deklariert werden sollten. Wie ich bereits sagte, sehe ich Tupeltypen nicht als geeignetes Gerät für Typsequenzen beliebiger Länge im Typsystem. Ich bin immer noch nicht überzeugt, dass dies für die gewünschte Expressivität erforderlich ist.

Ich würde zustimmen, dass es ausdrucksvoller sein könnte, aber der 'Spread'-Operator in der Position der Typparameter beschränkt uns darauf, Rest-Argumente nur einmal abzufangen, genauso wie wir Rest-Parameter nicht zweimal haben können. Wenn also <...T> und <A, B, C> , wird T sie als [A, B, C] fangen. Und wir könnten <...T, ...U> nicht ausdrücken, da es mehrdeutig wäre - [A, B, C], [] oder [A, B], [C] oder ... etc.

Nehmen wir an, ich möchte eine Funktion mit folgendem Verhalten ausdrücken:

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, ich sehe jetzt, wie du darüber denkst. Es hört sich so an, als ob das, was Sie vorschlagen, tatsächlich ein anderes Merkmal ist, als ich dachte. Anstatt ein neues Konstrukt zum Erfassen einer Folge von Typparametern hinzuzufügen, möchten Sie nur, dass Tupeltypen gespreizt werden können, da sie bereits eine Folge von Typen darstellen. Auf diese Weise ist es möglich, mehrere Tupel unterschiedlicher Länge transparenter zu übergeben.

In Javascript ist es eher function foo([...rest]) { } statt function foo(...rest) { } .

Das macht für mich jetzt mehr Sinn, danke für die Erklärung. Ich denke, das ist ein vernünftiger Ansatz.

@JsonFreeman Genau!

@JsonFreeman Frage: Warum sollte [1, 2] [number] erfüllen? Das kommt mir sehr seltsam vor. Dass tatsächlich funktioniert, wäre sehr überraschend. Es ist überhaupt nicht typsicher.

Nicht, dass ich etwas gegen die Verwendung von Tupeln für variadische Typen hätte (ich bin auch neutral, um ehrlich zu sein).

@isiahmeadows inwiefern ist [1, 2] nicht für [number] ersetzbar ? Es ist definitiv ein Untertyp. Genauso ist { x: 1, y: 2 } ein gültiges { x: number }

Okay. Ich gebe teilweise zu, berücksichtige aber Function.prototype.apply, die ein Tupel von Argumenten akzeptiert.

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

Wenn der Aufrufer bei zu vielen Argumenten einen TypeError auslöst, führt die Übergabe zu vieler zu einem Laufzeitfehler, nicht zu einem Kompilierungsfehler, wie er sollte.

Ist es nicht ziemlich selten, dass eine JS-Funktion TypeError auslöst, wenn zu viele Argumente übergeben werden? Was sind einige Beispiele?

@isiahmeadows als abstraktes Beispiel habe ich verstanden, dass der Fehler, über den Sie sich Sorgen machen, der ist:

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.

Ist das korrekt?

@sandersn , ich denke, dass TypeError bei zu vielen Argumenten gegen den Geist des JS verstößt, da wir normalerweise Funktionen mit weniger formalen Argumenten übergeben als tatsächliche, die an diese Funktion übergeben werden. Wir nutzen sie einfach nicht. Zum Beispiel Array.prototype.forEach

Wie wäre es mit Funktionscurry? Das ist wahrscheinlich viel häufiger, bei Ramda
und lodash/fp.

Am Montag, 29. Februar 2016, 13:45 Uhr schrieb Anatoly Ressin [email protected] :

@sandersn https://github.com/sandersn , ich denke, dass TypeError auch eingeschaltet ist
viele Argumente verstoßen gegen den Geist des JS, da wir
Übergeben Sie normalerweise Funktionen mit weniger formalen Argumenten als tatsächliche, die dies tun
an diese Funktion übergeben werden. Wir nutzen sie einfach nicht. Zum Beispiel
Array-Prototyp.fürEach


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190327066
.

@isiahmeadows Ich würde sagen, dass Currying basierend auf arguments.length sehr instabil und fehleranfällig zur Laufzeit ist. Echtes Curry ist extra argumentativ:

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

Wenn ich meine Funktion mit fester Signatur als Callback irgendwohin übergebe, denke ich folgendermaßen darüber nach: 'meine Funktion erwartet _mindestens_ diese Argumente'

Was ist mit Dingen wie 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))

Das ist in der funktionalen Programmierung sehr üblich. Und das letzte weglassen
Argumente sind ziemlich verbreitet.

Am Montag, 29. Februar 2016, 13:52 Uhr schrieb Anatoly Ressin [email protected] :

@isiahmeadows https://github.com/isiahmeadows Ich würde sagen, dass Currying
basierend auf der aruments.length ist sehr instabil und laufzeitfehleranfällig.
Echtes Curry ist extra argumentativ:

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


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Wenn Sie das als Rückruf an beispielsweise map möchten (eine Liste bearbeiten
von Listen), möchten Sie es wahrscheinlich kurieren.

Am Montag , 29. Februar 2016, 13:59 [email protected] :

Was ist mit Dingen wie 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))

Das ist in der funktionalen Programmierung sehr üblich. Und das letzte weglassen
Argumente sind ziemlich verbreitet.

Am Mo, 29.02.2016, 13:52 Uhr Anatoly Ressin [email protected]
schrieb:

@isiahmeadows https://github.com/isiahmeadows Ich würde sagen, dass Currying
basierend auf der aruments.length ist sehr instabil und laufzeitfehleranfällig.
Echtes Curry ist extra argumentativ:

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


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Ich denke, es geht hauptsächlich darum:

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]

Deshalb habe ich #6229 vorgeschlagen

Die Frage, ob [1, 2] [number] [1, 2] erfüllt, ist eine berechtigte Frage und Diskussion. Aber was hat das mit der Funktion "spreizbare Tupel" zu tun?

Es geht darum, ob eine variadische Anwendung von Tupeln zusätzliche Argumente ignorieren soll
oder nicht. Diese überladene Funktion sollte mehr auf mein Anliegen eingehen.

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)

Am Montag, 29. Februar 2016, 18:47 Uhr schrieb Jason Freeman

Die Frage, ob [1, 2] [Zahl] erfüllt, ist eine berechtigte Frage
und debattieren. Aber was hat das mit der Funktion "spreizbare Tupel" zu tun?


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

Und deshalb würde ich aus praktischen Gründen eher Ruheparameter-ähnlich bevorzugen
variadische Typen.

Am Mo, 29.02.2016 , 19:00 [email protected] :

Es geht darum, ob eine variadische Anwendung von Tupeln zusätzliches ignorieren sollte
Argumente oder nicht. Diese überladene Funktion sollte mehr von meiner
Anliegen.

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)

Am Mo, 29. Februar 2016, 18:47 Jason Freeman [email protected]
schrieb:

Die Frage, ob [1, 2] [Zahl] erfüllt, ist eine berechtigte Frage
und debattieren. Aber was hat das mit der Funktion "spreizbare Tupel" zu tun?


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

@JsonFreeman das liegt A , B und T = [A] erlaubt ist, dann wird [...T, B] [A, B] konstruieren " (was implizit vorgeschlagen wird), dann wäre es nicht mit dem Array/Tupel-Spreizoperator ausgerichtet. Bei gegebenem var a: [A] und var b: B kann nicht nachgewiesen werden, dass der Typ des Ausdrucks [...a, b] vom Typ [A, B] . Nach den aktuellen Tupelregeln konnte nachgewiesen werden, dass es vom Typ [A, A|B] .
Macht es Sinn für dich? Oder ich kann eine Vergleichstabelle erstellen, um diese Nichtübereinstimmung hervorzuheben.

@Igorbek Ich verstehe, was du a eine unbekannte Länge, während der Typ [A] eine bekannte Länge hat. Dies war einer der Gründe, warum es mir anfangs unangenehm war, Tupeltypen für diesen Zweck zu verwenden. Aber ich bin mir nicht sicher, ob es ein ernsthaftes Problem ist.

@isiahmeadows Ich Ruhetyps klarer? Wenn Sie mehr Argumente als Typargumente haben, kann dieselbe Frage gestellt werden.

Die typsichere Lösung wäre konsistenter mit dem Rest der
Sprache, wenn sie die Argumentsyntax nachahmt.

Mein Punkt ist, wenn Sie einen Ruheparameter effektiv verteilen, erhalten Sie
genau die Argumenttypen und nicht mehr. Curry-Funktionen haben eine Rückkehr
type abhängig vom Argumenttyp. Wenn Sie also eines zu viele Argumente anwenden
um eine Curry-Funktion teilweise anzuwenden, erhalten Sie eine ganz andere
Typ. Die Behandlung von Resttypen wie Tupeln würde stattdessen zu Laufzeitfehlern führen.
die nie gut sind.

Am Di, 1. März 2016, 06:07 schrieb Jason Freeman [email protected] :

@Igorbek https://github.com/Igorbek Ich verstehe, was Sie sagen.
Das liegt letztlich daran, dass der Compiler über perfektes Wissen verfügt
der Typen, mit denen es zu tun hat, aber keine perfekte Kenntnis der Werte. In
insbesondere hat in Ihrem Beispiel der Wert a eine unbekannte Länge, während der
Typ [A] hat bekannte Länge. Dies war einer der Gründe, warum ich anfangs war
unangenehm, für diesen Zweck Tupeltypen zu verwenden. Aber ich bin mir nicht sicher
es ist ein ernstes problem.

@isiahmeadows https://github.com/isiahmeadows Ich verstehe, was du
über, aber warum ist das Problem mit Parametern des Ruhetyps klarer?


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190667281
.

@isiahmeadows können Sie Beispielcode für das Currying-Problem geben?

Ich denke immer noch, dass Sie, selbst wenn Sie Parameter vom Typ rest verwenden (wofür ich bin), explizit entscheiden müssen, keine übermäßigen Argumente zuzulassen, aber ich stimme @isiahmeadows zu, dass Sie wahrscheinlich sollten.

@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

Ich werde meine Variante auch hinzufügen. Sehen wir uns das Beispiel für Variadic Curry aus dem Vorschlag an:

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

Also habe ich angefangen es zu benutzen:

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 Unabhängig davon, ob sie als Tupeltypen dargestellt werden, klingt es so, als ob Sie

@Igorbek Ich denke, Ihr Beispiel ist

@JsonFreeman Es ist eher, dass ich diesem Verhalten widerspreche:

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)

Ist das klar?

@isiahmeadows sollte eigentlich funktionieren

@JsonFreeman
Ich finde, es sollte nicht. Das ist mein größter Einwand. Ich halte es für potenziell gefährlich, wenn es so ist.

Frage: Was sollte der abgeleitete Rückgabetyp von ret ?

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

Das ist eigentlich ziemlich wichtig.

Das letzte Beispiel scheint definitiv nicht zu funktionieren. Im Wesentlichen benötigen Sie einen Mechanismus, um Sequenzen von Typen auszurichten, wenn function.bind wirklich richtig funktioniert. Sie bräuchten so etwas wie Vereinheitlichung, bei der die Typen der zu bindenden Argumente mit den Argumenten der ursprünglichen Funktion abgeglichen werden und dann der Rest im Rückgabetyp ist.

Das heißt, es scheint nichts in dem, was vorgeschlagen oder diskutiert wurde, damit umzugehen (unabhängig davon, ob zusätzliche Argumente von Tupeln zulässig sind), obwohl es möglich ist, dass ich etwas übersehen habe.

Ich denke, das größte Problem ist, dass eine Art Tupelmusterabgleich, bei dem der Typ jedes Parameters mit den Spread-Typen abgeglichen wird, mit den Typparametern (a la LiveScript/CoffeeScript-Argumente) durchgeführt werden muss, um dieses Problem zu beheben. Anders ist es wohl nicht möglich. Und wie kompliziert es ist, viel Glück bei der Umsetzung. :Lächeln:

@JsonFreeman

Genauer gesagt, es erfordert eine nicht strenge (im Sinne von eifrig vs faul) Typprüfung, um zu funktionieren. Ich denke auch, dass dies wahrscheinlich sowieso eine nützlichere Erweiterung ist als nur variadische Typen, da sie die Tür für viele andere nützlichere Dinge wie selbstrekursive Typen öffnet.

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

Glücklicherweise sollte die nicht strikte Typprüfung eine rein nicht brechende Änderung sein, da jetzt nur Code funktioniert, der zuvor nicht überprüft wurde.

Das ist wahrscheinlich das Größte, was die korrekte Eingabe von Function.prototype.bind blockiert, abgesehen

Das ist eine interessante Verbindung. Ich bin jedoch nicht überzeugt, dass sie verwandt sind. Das Problem mit rekursiven Typen ist eine Folge der Caching-Richtlinie für Generika und der Darstellung von Typaliasen im Compiler. Alle Informationen sind vorhanden, es ist nur das Design des Compilers, das im Weg steht.

Beim Tupelmusterabgleich können Sie nicht immer wissen, wie viele Argumente mit dem Tupel abgeglichen werden. Wenn Sie ein Array auf die Argumente von bind verteilen, wissen Sie nicht, wie viele im resultierenden Callback übrig bleiben.

@JsonFreeman , glauben Sie, dass als Schritt der Annahme des Arguments der Spread-Operator-Vorschlag #6229 zuerst in Betracht gezogen werden muss?

@JsonFreeman

Und eine nicht strenge Überprüfung der Typen würde genug Faulheit ermöglichen, um dieses Problem mit Function.prototype.bind einfacher zu beheben. Bei einer solchen Faulheit könnten Sie diesen Typ mit folgendem erreichen (was eine Tupel-Syntax erfordert, um sie zu sequenzieren, es sei denn, mehrere Restparameter sind in einer Typdeklaration in Ordnung):

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

Warum sollte dies eine nicht strenge Typprüfung erfordern, um daraus abzuleiten? Sie müssen den Resttyp Schritt für Schritt herleiten, um die Funktion zu überprüfen. Unter folgenden Bedingungen müsste es folgendermaßen überprüft werden:

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

So funktioniert die nicht strikte Typprüfung. Es leitet Typen nach Bedarf ab, nicht in dem Moment, in dem es sie sieht. Sie können (und sollten) die beiden Durchgänge kombinieren, damit falsche Typen scheitern. Beispiel:

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

In diesem Fall sollte der erwartete Parameter der erste sein, der in dieser Runde abgeleitet wird, wobei eine Tiefeniteration durchgeführt wird. In diesem Fall war das erste bei dieser Suche abgeleitete eine Zeichenfolge, und Zeichenfolgen können keine Symbole zugewiesen werden, sodass dies damit fehlschlägt.


Aus diesem Grund und dem Versuch, Function.prototype.apply , hat sich meine Meinung zur Verwendung von Tupeln zum Anwenden von Pausentypen geändert.

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

Noch ein paar Anmerkungen:

  1. Es muss eine Möglichkeit geben, Arrays und Tupel als Parameter vom Typ Rest zu verteilen.

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

  1. Es muss zwei separate Typen für Konstruktoren und Callables geben, wobei Functions die Vereinigung der beiden ist. Aufrufbare Objekte sollten die aufrufbare Schnittstelle implementieren, Klassenkonstruktoren sollten die konstruierbare Schnittstelle implementieren und ES5-Funktionen sollten die Vereinigung der beiden implementieren.
  2. Function.prototype.bind und Freunde sollten alle Überladungen für die Funktion überprüfen. Wenn es mehrere dieser Arbeit gibt, sollte es eine Vereinigung aller von ihnen zurückgeben.

Diese Typparameter in Ihrem Beispiel sind jedoch nicht wirklich die Typparameter der Bindungssignatur. Sie gehören zum Typ Funktion. Aber ja, die Idee ist, dass Sie dies schreiben können, wenn Sie zwei Rest-Parameter verwenden oder zwei Parameter vom Rest-Typ in einem Tupel verteilen könnten.

Damit die Signatur von bind flexibel genug ist, muss die Grenze zwischen ...X und ...Y pro Aufruf festgelegt werden. Es müsste abgeleitet werden. Es wäre jedoch ein Problem, wenn eine Signatur ...X isoliert verwenden würde. In diesem Fall wird die Grenze nicht festgelegt. Zum Beispiel:

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

Und Überladungen sind ein ziemliches Problem für den Function-Typ. Ich glaube nicht, dass Sie den Union-Typ elementweise für jedes Argument verwenden möchten, da dies das Mischen und Abgleichen von Parametern zwischen Überladungen ermöglichen würde. Ist es das, was du meintest?

@JsonFreeman

_TL;DR: Springt zum horizontalen Zeilenumbruch. Ich habe eine neue, praktischere Idee._

  1. Ja, ich bin mir bewusst, dass sie wirklich auf Function selbst gehören.
  2. Diese Art von Problem ist der Grund, warum ich sagte, dass eine nicht strikte Typübereinstimmung (im Sinne von Haskell) notwendig ist. Sie können den Typ normalerweise nicht eifrig auflösen, da dies eine iterative, träge Suche erfordern würde. Es ist möglich, algorithmisch zu bestimmen, aber Sie müssen Dinge im Auge behalten, die normalerweise nicht in C++ verfolgt werden müssten.
  3. Wenn die beiden Argumente isoliert sind (wie in Ihrem Beispiel), sollte sich der Compiler beschweren. Und diese Situation kann mit einer Abhängigkeitsanalyse auf Typebene jedes variadischen Arguments in der Schnittstelle/was auch immer erkannt werden. Es ist auch nicht trivial, aber es kann überprüft werden, wenn man die Typdeklaration selbst liest (eigentlich kurz danach).

Obwohl ich auch denke, dass es etwas praktikabler sein könnte, solche Situationen nur für die fragliche Methode zu definieren. Es wird auch viel einfacher und schneller sein, potenzielle Probleme zu erkennen, auf die Sie hingewiesen haben.

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
}

Es gibt ein potenzielles anderes Problem, das viel schwieriger zu lösen sein wird (und warum ich denke, es sollte auf 2, nicht auf _n _ Divisionen beschränkt werden):

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

_Entschuldigung, wenn ich hier zu tief in die CS-Theorie eintauche..._

Ja, ich denke, das ist die richtige Idee. Es ist nicht schön, aber ich kann mir keine andere Möglichkeit vorstellen, bind richtig einzugeben, wenn ich die Typargumente von Function . Das Letzte ist, dass eine Grenze abgeleitet werden muss. Und ich stimme zu, dass es auf 2 Buckets beschränkt sein sollte, damit Sie 1 Grenze ableiten müssen, anstatt einer beliebigen Anzahl von Grenzen, die kombinatorisch explodieren können.

Es gibt wahrscheinlich noch mehr Probleme, an die wir nicht gedacht haben.

@JsonFreeman Ein weiteres Problem sind Dinge wie curry . Mir ist noch nichts eingefallen, was das richtig schreiben kann. Und es wird eine Weile dauern, bis ich es kann. Ich müsste einige ernsthafte Hacking-Angriffe im Haskell-Stil durchführen, um einen solchen Prozess zu entwickeln.

Denken Sie darüber nach, wie ein Vorschlag mit einigen Bluebird-Funktionen funktionieren könnte.

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

Haben wir eine Lösung für das obige all<...T>(promises: [...PromiseLike<T>]): Promise<...T>; ?

@DerFlatulator

Siehe meinen großen Kommentar im PromiseConstructor. Ich habe auch Ihre Promise-Benutzeroberfläche korrigiert, um meinem Vorschlag etwas näher zu kommen.

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

Wenn [...Foo<T>] zu [Foo<T1>, Foo<T2>, /*... Foo<TN>*/] , ist dann [...Foo<T,U>] ein Syntaxfehler oder eine kombinatorische Erweiterung?

@DerFlatulator

  1. Wenn genau einer von T oder U ein Restparameter ist, wird er normal expandiert. Angenommen, T ist ein Restparameter, dann wäre es [Foo<T1, U>, Foo<T2, U>, /*... Foo<TN, U>*/] .
  2. Wenn beide Ruheparameter sind und ihre Länge korrekt abgeleitet werden kann, sollte es eine kombinatorische Expansion sein (also ... Länge von T mal Länge von U).
  3. Wenn keine Restparameter vorhanden sind, handelt es sich um einen Syntaxfehler.

Beachten Sie, dass ich aus praktischen Gründen mehr als 2 Ruheparameter stark ablehne und dass die Ruheparameter, wenn sie aufgeteilt werden müssen, nur pro Methode aufgeteilt werden sollten. Etwas wie das:

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
}

_(Wenn mir jemand eine bessere Syntax einfallen lässt, bin ich ganz Ohr. Ich mag es nicht, aber mir fällt nichts ein, was nicht visuell widersprüchlich ist.)_

@isiahmeadows

Bei 2., in welcher Reihenfolge würde der Ausbau erfolgen?

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

Oder umgekehrt:

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

Würde diese Mehrdeutigkeit nicht zu Verwirrung führen? Vielleicht wäre es sinnvoll, sich auf eine Dimension zu beschränken.


Nur ein alternativer Vorschlag für die geteilte Syntax:

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

Ich würde das zweite erwarten. Und ich bezweifle, dass es zu viel Verwirrung stiften würde, denn solange es konsequent ist, würden sich die Leute schnell daran gewöhnen. Es ist auch ein ungewöhnlicher Randfall, auf den in der Praxis nur Leute wirklich stoßen würden, die wissen, was sie tun, oder Leute, die die Notwendigkeit überhaupt in Frage stellen sollten.

Ich betrachte es auch, während Sie das erste erweitern, dann das zweite für jeden Teil des ersten. Wie dieser Pseudocode:

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

Iteration über einige der oben genannten Ideen ...

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

Hier ist [...TBound, ...TUnbound] = [...TArgs] gültig, da die Länge von ...TBound aus der Länge von args . Es erlaubt auch, den Typ von TThis ändern.

Ein Problem bei diesem Ansatz besteht darin, dass Sie this einmal binden können, zum Beispiel:

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>

Der abgeleitete Typ von y ist falsch, er sollte Function<number, IBar> lauten. Ich bin mir nicht sicher, ob dies ein Problem ist oder nicht, aber um es zu lösen, müsste Logik in die <T> Syntax eingeführt werden.

Option 1

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

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

Dies würde jedoch wahrscheinlich den Rahmen dieses Vorschlags sprengen.

Ich denke nicht, dass wir solche Erweiterungen mit dem Typ-Spread-Operator zulassen sollten. Ich denke an den Spread-Operator als "Klammerentferner", der absolut auf den Array-Spread-Operator und den Objekt- / Eigenschaften-Spread-Operator ausgerichtet ist (Vorschlag der Stufe 2). Vergleichen Sie einfach:

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

Sie schlagen vor, es zu erweitern, um neue Typen zu konstruieren:

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

Es ist ein ganz anderer Betrieb. Wenn Sie so wollen, könnte es durch Konstrukte höherer Ordnung modelliert werden, wie zum Beispiel:

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

@Igorbek Wie wäre es mit einem Operator, um zu bestimmen, was erweitert wird?

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

Wobei ...Foo<<strong i="9">@T</strong>, U> zu [Foo<T1,U>, /*...*/, Foo<TN,U>] .

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

Einige Syntaxalternativen:

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

Ich stimme @Igorbek hier zu. Zumindest in diesem Stadium scheint die Abbildung von Typenfolgen keine Priorität zu haben, da wir immer noch versuchen, das grundlegendere Problem der variadischen Typparameter zu lösen.

Ich habe kein großes Problem damit, es zu verbieten (zumindest anfangs), da das Verhalten dafür ziemlich nutzlos ist und zwei verschiedene Leute sogar zwei sehr unterschiedliche Dinge erwarten könnten. Ich stimme @Igorbek zumindest jetzt zu, da TypeScript zuerst ein map ping einen Typ). Und höhere Auftragstypen sind nicht gerade etwas, das man einfach anschrauben kann.

Also definitiv :+1: dafür, das zu verbieten, wahrscheinlich für eine ganze Weile. Auch wenn es nett zu haben ist, ist es höllisch kompliziert zu implementieren und wäre ein kompletter Hack, da TypeScript kein funktionales, typsicheres Typsystem verwendet.

Ich komme etwas spät, aber ich stimme auch

Das Packen von Typen in ein Tupel scheint mit der Verwendung des Spread-Operators in Typescript konsistent zu sein:

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

Was es viel einfacher macht, über <...T_values> = [T1, T2, T3, etc...] nachzudenken.

Während C++ den Spread-Operator zum Packen und Ellipsen zum Entpacken verwendet, ist die Verwendung von Spread für beides konsistenter mit 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 was wäre der Sinn von all dem ohne Mapping?

Wie in Nr. 1336 erwähnt, wie wäre es mit einem Variadic Array.flatten ?

@jameskeane Diese erste Hälfte war die ursprüngliche Idee, aber sie deckt nicht den Fall eines mittleren

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

Es deckt auch Function.prototype.apply vs. Function.prototype.call sehr gut ab.

Was #1336 betrifft, könnte es ähnlich wie folgt implementiert werden:

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

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

Ich habe aufgeholt und erkannt, dass ich naiverweise annahm, dass Tupeltypen eine strikte Länge haben; welche imo ist die intuitivste. Angenommen, wir erhalten Tupeltypen mit strikter Länge (#6229), was sind die Probleme?

@isiahmeadows Wird es in Ihrem obigen Beispiel für den Fall des mittleren Längentupel gelöst? Ich lese ...rest: [...T, Baz] genauso wie beim Auspacken von arr = [...other, 123] . Dies ist das gleiche Problem, das Sie mit curry angesprochen haben, richtig?

Sind apply und call nicht von sich überschneidenden Typen abgedeckt? (Nicht, dass ich wirklich den Wert darin sehe, die Typen auf der Function Schnittstelle zu haben).

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

Der aktuelle variadic-Vorschlag geht davon aus, dass #6229 tatsächlich akzeptiert wird (dh Tupel sind standardmäßig strikt).

Bei func.apply , func.bind , func.call und _.curry ist das einzige Problem bei func.bind , _.curry , und Freunde, oder allgemeiner alles mit teilweiser Anwendung. Sie müssen auch in der Lage sein, auszuwählen, welcher Restparameter getrennt werden soll, und dies kann nur pro Methode erfolgen.

call und apply sind ziemlich einfach:

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 wäre schwieriger. Die Split-Parameter müssten bei Bedarf angepasst werden, im Gegensatz zu eifrig, wie es jetzt der Fall ist, bis die erste Split-Hälfte vollständig ausgepackt ist. Dies sollte als Syntax implementiert werden, damit der Compiler es unterscheiden und den Typ richtig erkennen kann, ohne etwas auszuwerten.

// 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 wäre extrem schwierig, da es wissen muss, dass f(1, 2, 3) === f(1, 2)(3) === f(1)(2, 3) === f(1)(2)(3) . Es muss nicht nur die Möglichkeit gegeben sein, einen Restparameter wie in bind in zwei Teile aufzuspalten, sondern es muss auch die Möglichkeit gegeben sein, sehr primitive Mustervergleiche pro Methode durchzuführen.

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

Ich glaube nicht, dass die Ergänzungen für curry es Turing-vollständig machen würden, aber es wäre nahe dran. Ich denke, das Wichtigste, was dies verhindert, wäre die Fähigkeit, Spezialisierungen eines bestimmten Typs abzugleichen (die C++, Scala und Haskell, drei Sprachen mit Turing-vollständigen Typsystemen, alle haben).

@sandersn Ich konnte oben kein Beispiel sehen, aber kann ich nach Einschränkungen für variadische Parameter fragen?

Betrachten Sie das folgende Beispiel:

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

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

_Siehe übrigens unter https://github.com/Microsoft/TypeScript/issues/7848 für eine Diskussion darüber, wie die Anforderung, dass X aufgelistet werden muss, möglicherweise fallengelassen wird_

Nun besteht hier möglicherweise eine gewisse Mehrdeutigkeit, ob die Einschränkung wie folgt lautet:

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

In diesem Beispiel gehe ich von 2 aus.

Werden diese Art von Einschränkungen (1 und/oder 2) möglich sein?

@myitcv 2 wäre wahrscheinlich der beste Weg, aber es wäre sinnvoll, nur die vorhandene Logik zum Überprüfen der Einschränkung wiederzuverwenden.

Nun ... mir ist gerade etwas klar geworden: Wie würden Arrays mit variadischen Typen aussehen? Oder genauer, welcher Typ ist arg unten?

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

Ich nehme an, Sie fragen nach dem Elementtyp von args . Bei Tupeln ist dies meiner Meinung nach normalerweise der Unionstyp der Elemente. Ich kann mir keinen besseren Weg vorstellen, das zu tippen.

@sandersn können Sie den Status dieser Funktion kommentieren? Ich habe das Gefühl, dass es viele Diskussionen gegeben hat, aber es klingt nicht so, als ob es einen definitiven Plan für das Feature gäbe, ist das richtig?

@JsonFreeman Ich habe speziell gefragt, was arg . Meiner Meinung nach sollte es any für mein ursprüngliches Beispiel sein und Item<T> mit unten (mit dem F-begrenzten T):

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

Dadurch können die Typen lokal aufgelöst werden. Sie kennen die Typen nicht im Voraus, und dies beschleunigt die Kompilierung enorm, da Sie die Typen innerhalb der Funktion nicht für jeden Aufruf dieser Funktion berechnen müssen. Beachten Sie, dass typeof arg ausreicht und wahrscheinlich kürzer ist, wenn Sie nur den Typ eines einzelnen Arguments benötigen.

Entschuldigung, für das ursprüngliche Beispiel meinte ich, dass der Typ T . Für Ihr zweites Beispiel denke ich auch, dass es T sein sollte.

Ich meinte Item<any> im zweiten ... Entschuldigung.

Als ich sagte, es sollte T sein, ging ich davon aus, dass T ein Typ ist, aber ich denke, der springende Punkt dieser Funktion ist, dass T kein Typ ist (glaube ich). Also ja, ich denke, es sollte in Ihren Beispielen any und Item<any> .

Aber im Allgemeinen bin ich gespannt, wie aktiv das Team dieses Feature für nach 2.0 in Betracht zieht. Ich habe keine starke Meinung, frage mich nur.

Der Grund, warum es meiner Meinung nach nicht unbedingt T ist, dass Sie nicht wissen, was T ist. Es sei denn, Sie meinen natürlich, dass die Variadic T entweder einen einzelnen Typ der Variadic-Typliste darstellt oder, wenn sie verteilt wird, die Liste selbst, dh T ist der Untertyp aller übergebenen Argumente das Argument ...T und [...T] ist T[] zuweisbar.

Oder, um zu verdeutlichen, was ich in all dem unklaren Jargon meine, hier ist, was ich in Bezug auf Code meine:

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

Das wäre aber wahrscheinlich sinnvoller und viel flexibler.

Es steht immer noch auf unserer Nice-to-have-Liste, aber es ist in erster Linie für Bibliotheksautoren von Interesse und hat eine anständige Problemumgehung – _n_ Überladungen –, also arbeite ich nicht aktiv daran. Wenn ich raten müsste, würde ich sagen, dass 2.1 möglich, aber nicht wahrscheinlich ist.

Wenn/wenn wir uns verpflichten, Objekt-Rest/-Spreizung (#2103) richtig zu unterstützen, dann könnten variadische Arten nahe genug an der Verbreitung von Typen sein, um zu rechtfertigen, dass sie alle auf einmal ausgeführt werden. (Spread-Typen sind eine Variante von Objekttypen, die wie { ...T, x: number, ...U, y: string, ...V } aussehen.)

Ich möchte nur erwähnen, dass der n overloads Workaround für Klassen oder Interfaces nicht funktioniert, was mein besonderes Interesse an dieser Funktion ist.

@sandersn Würde eine Pull-Anfrage für "_n_ Überladungen" für bind , apply und call in Funktionen mit this Typisierung eingeladen werden? Ich denke, das wäre für viele ein akzeptabler vorübergehender Kompromiss und könnte bei einigen Projekten einige Fehler im Prozess auffangen.

@isiahmeadows

Der Grund, warum es meiner Meinung nach nicht unbedingt T sein sollte, ist, dass Sie nicht wissen, was T ist.

Es schien mir, dass es Übereinstimmung gab, dass T ein Tupeltyp der variadischen Typen ist. In Ihrem ursprünglichen Beispiel wäre der Typ von arg derselbe wie der Tupelelementtyp (wie @JsonFreeman bemerkte, "der Vereinigungstyp der Elemente"): Stellen Sie sich

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]

Unabhängig von diesem Vorschlag sollte es meiner Meinung nach eine Möglichkeit geben, den "Elementtyp" eines Tupels darzustellen. Das könnte eine andere Verwendung für Spread sein, zB wie oben ...T :: number|string|boolean ; dass das Verteilen des Tupeltyps zu seinem Elementtyp führt.

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

In diesem Sinne Ihre anderen Beispiele:

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

  }
}

Um mein ursprüngliches Ziel nicht aus den Augen zu verlieren, wollte ich ein stark typisiertes Promise.all ...

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 Nun, da andere angeforderte Funktionen davon abhängen, könnte die Priorität vielleicht erhöht werden? bind , call usw. die Eingabe hängt davon ab und die ES-Bind-Syntax, ob/wann sie herauskommt, hängt davon ab . :)

Nicht, dass dies besonders konstruktiv wäre, aber ich würde mich sooo freuen, wenn diese beiden Funktionen es in 2.1 schaffen würden. Ich kenne mindestens eine Bibliothek (RxJS), bei der nicht nur die Codebasis selbst durch diese Funktionen verbessert würde, sondern auch das Konsumieren von Code deutlich weniger umständlich und anfällig für Fehler wäre (jede dritte Person, die mit Angular 2 beginnt, wird von fehlenden Importen gebissen für Operatoren, die in den beobachtbaren Prototyp gepatcht sind). Es wäre wirklich eine bahnbrechende Funktion für Leute, die wartbaren Funktionscode schreiben möchten.

Könnte dies verwendet werden, um eine vollständige Typdefinition für _.extend bereitzustellen, deren Rückgabetyp die Schnittmenge aller Parameter ist?

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

Nicht so wie es steht. Es braucht eine Erweiterung des Vorschlags, die Details zu einem neuen Operator für variadische Arten enthält – wahrscheinlich & genannt. @kitsonk hat diesen Operator früher in diesem Kommentar vorgeschlagen .
Im Moment befindet sich diese Funktion unter ein paar anderen, unmittelbar wichtigen Dingen, daher habe ich mir diesen Vorschlag eine Weile nicht angesehen.

#10727 liefert zwar keine vollständigen Variadic-Arten, ist aber Teil der Lösung (und wird wahrscheinlich die Herausforderungen angehen, die wir (@dojo) haben).

Gut zu hören! Obwohl es immer noch nicht wirklich variadische Arten sind. :( Als ich diese Woche zum Beispiel versuchte Object.assign , bin ich so weit gekommen:

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

Beachten Sie, dass die Syntax der "sechs Punkte" eine Objektverteilung einer Tupeltyp-Variablen ist, die wir oben nicht wirklich besprochen haben.

@sandersn

Insbesondere bei Object.assign könnte es auf diese Weise getippt werden und technisch eine Teilmenge erfassen (wenn auch etwas zu schwach abgeleitet), da sie ihr Ziel mutiert (dafür müsste man einen Referenzpunkt haben):

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

Der Fehler dabei ist, dass es sein Ziel mutiert und seinen strukturellen Typ ändert.

@isiahmeadows, dann würde die Inferenz T auf den Typ von target ohne Abrechnungstypen von sources festlegen. Sie können es jetzt mit einer nicht-variadischen Version versuchen:

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

Wie bereits erwähnt, verwendet assign _einen Spread-Typ_ #10727 und kann so definiert werden:

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

_Hinweis: Ich bestehe immer noch auf tupelbasierter Syntax [...T] die für mich viel sinnvoller ist._

@sandersn Übrigens , gibt es ein Update, wann Variadic-Arten gelandet werden? Gibt es eine Chance, es in 2.2 zu sehen?
Und was die Syntax betrifft, akzeptieren Sie immer noch Feedback zur Syntax oder sind Sie sich alle einig?

Die Syntax und die Semantik auf niedriger Ebene haben noch keinen klaren Konsens.

Am Dienstag, 13. Dezember 2016, 13:26 Uhr schrieb Igor Oleinikov [email protected] :

@sandersn https://github.com/sandersn Übrigens , gibt es ein Update wann
variadic-Arten werden gelandet? Gibt es eine Chance, es in 2.2 zu sehen?
Und was die Syntax betrifft, akzeptieren Sie noch Feedback zur Syntax oder
da sind Sie sich alle einig?


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-266819647 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AERrBIa5fE8PSk-33w3ToFqHD9MCForRWks5rHuM5gaJpZM4GYYfH
.

Haben Sie eine Idee, wie der Status dieses Problems ist?

Also, an welche Optionen denkst du? Steht das im Team auf der Agenda? es ist der einzige schwache Teil des Typsystems, auf das ich immer wieder stoße. Ich habe zwei Anwendungsfälle. eine einfache und eine komplexe, aber allgemeinere.

einfach wäre es, einen Tuple extends any[] Supertyp hinzuzufügen, der nur von Tupeltypen subtypisiert werden kann. Da Spreads any[] Untertypen sein müssen, würde dies funktionieren:

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

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

derzeit ist ...args: T[] nur am Ende von Unterschriften erlaubt.

der komplexe Anwendungsfall würde ...args: Tuple benötigen, um überall innerhalb einer Signatur legal zu sein (was kein Problem ist, da Tupel eine feste Länge haben):

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

Ja, ich habe gestern gerade erst mit TypeScript angefangen und dies hat es bereits unmöglich gemacht, meine Funktionen automatisch einzugeben (ich kann es natürlich immer noch manuell tun), weil ich einen einzigen Decorator verwende, den ich verwende, um meine Funktionen einzuschließen (was das Erste ist) Ich habe versucht, mit zu beginnen!):

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

Effektiv macht der Dekorator nur, dass eine Funktion auch als Methode aufgerufen werden darf, damit sie als Methode angehängt werden und identisch funktionieren kann. Als grundlegendes Beispiel ist hier ein schlechtes Beispiel, das den Array Prototyp patcht mit einer Basisversion von 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])

Nun ist es hoffentlich offensichtlich, dass der Typ von flatMap (nicht _flatMap ) ist:

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

Ich habe jedoch keine Möglichkeit, types zu portabel hinzuzufügen, da ich den Parametertyp nicht aus _flatMap extrahieren kann, um ihn dann in der Typdefinition der dekorierten Funktion zu verwenden schreibe sowas wie:

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

Ich wollte dies nur teilen, da es meine ersten Erfahrungen mit TypeScript zeigt und vielleicht einen weiteren Fall zeigt, warum variadische Generika wichtig sind.

@sandersn :

es hat eine anständige Problemumgehung - n Überladungen

Obwohl dies technisch nicht falsch ist, finde ich, dass dies die Realität hier nicht vollständig widerspiegelt. Ja, technisch konnte das Fehlen davon nicht verhindern, dass Überladungen eine in diesem Thread erwähnte Funktion eingeben; und doch hat diese kleine Unannehmlichkeit dazu geführt, dass es bisher keine solchen überlastungsbasierten Lösungen in lib.d.ts hat.

Und tatsächlich waren viele in diesem Thread verzweifelt genug, ihre jeweiligen Funktionen in Angriff zu nehmen, um noch mehr Syntax vorzuschlagen, die ursprünglich nicht Teil dieses Vorschlags war, einschließlich Ihrer ...... sowie ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> und <PromiseLike<T*>> .

Ich denke, dies zeigt, dass wir alle versuchen, hier Probleme anzugehen, wir teilen das allgemeine Gefühl, dass wir eine leistungsfähigere Syntax wie diese brauchen, und hoffen, dass jeder Weg, den wir hier wählen, uns hilft, diese zu lösen.

Randbemerkung: Für Ramdas R.path wir eine Typisierung von tausenden Zeilen von Überladungen generiert, die immer noch die Tupelunterstützung vermissten (Permutationen wären noch viel härter explodiert) und die Kompilierung bei echten Projekten einfach nicht beendete nicht mehr. Kürzlich entdeckte Iteration als scheinbar praktikable Alternative dort (#12290).

Übrigens, afaik mussten Sie den Vorschlag von @Artazor und @Igorbek noch kommentieren. Was waren Ihre Gedanken dazu?

Ich möchte mit einer grundlegenden Implementierung wie dieser hier (plus #6606) argumentieren, dass wir fast alles tun können. Ich werde hier ein paar Lösungen anbieten, um dies zu veranschaulichen, aber ich bin offen für weitere Fragen.

Lassen Sie mich zunächst über einige Orte gehen , wo ein ... Betreiber implementiert werden könnte:

v ... für | Definition (einfangen) | verwenden (verbreiten)
-|-|-
Funktion | type Fn = (...args: any[]) => {} | type Returns = typeof fn(...MyTuple); (#6606)
Array | Tupel-Destrukturierung auf Typebene. kann technisch mit Indexzugriff + Spread (siehe rechts) + Rekursion emuliert werden. | type Arr = [Head, ...Tail];
Objekt | Objektdestrukturierung auf Typebene. nicht erforderlich, verwenden Sie einfach Omit , siehe #12215. | type Obj = { a: a, ...restObj }; (nicht notwendig, wie Overwrite , siehe #12215)
Generika | definieren type Foo<...T> um Foo<1, 2, 3> type Foo<...T> zu tun (erfasst [1, 2, 3 ] in T ). Spaß, aber ich weiß nicht, welcher Anwendungsfall dies erfordert. | definieren type Bar<A,B,C> um Bar<...[1,2,3]> type Bar<A,B,C> zu tun ( A = 1 usw.). dito, kenne keine Anwendungsfälle, die dies
Gewerkschaften (Bonus) | ? | type Union = "a" | "b"; type MyTuple = ...Union; // ["a", "b"] (Reihenfolge unzuverlässig, ermöglicht aber die Iteration von Unions/Objekten durch Tupel. Jedenfalls weit außerhalb des Geltungsbereichs.)

Es gibt also nur zwei ... Instanzen

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

Im Kontext von #6606 wird ein weiterer relevant: die Möglichkeit, einen Tupeltyp für die Funktionsanwendung zu entpacken, zB typeof f(...MyTuple) . Ich denke, diese reichen aus, um die schwierigeren Probleme zu lösen, von denen ich hier gehört habe. Um zu versuchen, hier einige Lösungen anzubieten:

@jameskeane :

Ich denke, es sollte eine Möglichkeit geben, den 'Elementtyp' eines Tupels darzustellen

Wenn Sie die Vereinigung ihrer Elemente erhalten möchten, lesen Sie meine 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

Dies sind beide nur verschiedene Versionen von Ramdas mergeAll . Keine sechs Punkte erforderlich!

@isiahmeadows :

Haben Sie eine Idee, wie der Status dieses Problems ist?
Die Syntax und die Semantik auf niedriger Ebene haben noch keinen klaren Konsens.

Wenn ich das richtig verstehe, haben Sie sich hauptsächlich Sorgen gemacht, ob der von einigen anderen angebotene Ansatz auch schwierigere Eingaben wie die bind Ihnen erwähnten curry und bind berücksichtigen würde. Hier ist meine Meinung zu diesem speziellen, nach ihrem Vorschlag.
Die Strategie ist ein bisschen ähnlich, sie betrügt die Tatsache, dass es schwer zu sagen ist, Typanforderungen für Parameter i~j von einem Funktionstyp in einen Tupeltyp zu extrahieren, indem die Typprüfung von Argumenten an die Funktionsanwendung verschoben wird.

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

Ja, ich habe eine Reihe von Helfertypen verwendet - nur versucht, mit dem auszukommen, was wir haben (+ stellen Sie sich vor, was wir mit etwas mehr tun könnten). Ich bin nicht so sehr gegen ...... , ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> oder <PromiseLike<T*>> . Aber IMO, auch nur ... hilft gerade, ein echtes Problem zu lösen, und ich würde es gerne sehen.

Bearbeiten: Ich habe die Argumentbeschränkung für bind gelöst.

Nur eine wahrscheinlich dumme Frage. Dies sieht sehr vielversprechend aus, um Curry-Funktionen korrekt eingeben zu können.
Aber für einige reale Projekte könnte man nicht viel Zeit damit verbringen wollen, stark funktionale, programmorientierte Codestücke einzugeben.
Da --strict standardmäßig in _tsconfig.json_ aktiviert ist, frage ich mich also, ob es eine Möglichkeit geben könnte, die Typprüfung für einen Teil des Codes zu deaktivieren (aus Faulheit oder Zeitmangel).
Aber wie gesagt, wahrscheinlich eine blöde Frage... ^_^

@yahiko00 irgendwie off-topic, aber verwenden Sie den Abschnitt exclude in tsconfig oder verschiedene tsconfig s auf verschiedenen Projektebenen.

Ich würde auch gerne einen anderen Vorschlag machen, könnten wir es so haben, dass & und | mit einem einzigen Tupelargument mit dieser Syntax funktionieren:

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

Der obige Vorschlag von

Ich möchte einen Haftungsausschluss hinzufügen, dass an diesem Vorschlag nicht aktiv gearbeitet wird. Aber ich habe genau die Art von Papier gefunden, die ich lesen wollte, als ich mich zum ersten Mal mit diesem Problem befasste :

Ich lasse das hier für zukünftige Referenzen.

Ich habe ein paar PRs geöffnet, um damit zu experimentieren:

  • [ ] #17884 Spreads in Tupeltypen (WIP)
  • [x] #17898 Restparameter extrahieren (bereit)
  • [ ] #18007 verbreitet sich im Typaufruf (WIP)
const c = 'a' + 'b';

Kann das Problem lösen? Der Typ von c ist 'ab' nicht string

Eine verwandte Frage zu StackOverflow: Expliziter letzter Funktionsparameter in TypeScript

@sandersn Ihr Vorschlag würde diesen Fall abdecken, soweit ich das sehen kann, ist das richtig?

Sollten wir mehr als 2 Jahre nach dem ursprünglichen Vorschlag noch hoffnungsvoll bleiben?

Hallo!
Ich versuche, einen Generator einzugeben, der eine variable Anzahl von Arrays verwendet und Elemente davon mischt und abgleicht, um ein neues Array zu erstellen.
Ich möchte diesen Generator in einer for...of Schleife verwenden, kann aber die Werte nicht richtig eingeben.
Code (es kann Fehler geben, da ich ihn noch nicht ausgeführt habe, aber das ist, was ich versuche):

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

Anwendungsbeispiel:

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

Ich kann keine Möglichkeit finden, für Ziel und Kind zu tippen, ohne eine Zwischenvariable zu erstellen.

So etwas wäre gut?

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

@Griffork Die korrekte Vorgehensweise, bis dieser Vorschlag implementiert wird, besteht darin, viele Überladungen für die Funktionen zu erstellen
Siehe zum Beispiel das Versprechen.Alle Typen
https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L41 -L113

Ich finde diese Syntax sehr verwirrend:

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

das fühlt sich für mich viel natürlicher an:

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

Zur Laufzeit ist ein Rest-Parameter ein Array, und wir können dies derzeit in TS tun:

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

Es erscheint also logisch, einfach die Einschränkung aufzuheben, dass args ein Array von T und stattdessen dem TS-Compiler zu ermöglichen, einen Tupeltyp für T , zB

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

Ich habe gesehen, dass einige Bedenken bezüglich Tupeln geäußert wurden und ich sie nicht alle vollständig verstehe, aber ich wollte nur berücksichtigen, dass es im aktuellen Vorschlag schwer zu verstehen ist, wann ein Entwickler ... in einer Typposition und Tupel sind viel intuitiver.

... macht für mich immer noch Sinn, um zwei Tupeltypen zu verketten, wie [...T, ...U] .

@felixfbecker
Der Vorschlag für

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

Wäre T ein dynamisch erstellter Tupeltyp. Wenn Sie also ein string und ein int an die Funktion übergeben, dann ist T [string, int] .
Dies ist besonders interessant, wenn Sie ein Muster wie dieses dynamisch ausdrücken möchten:

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.

Wenn Sie derzeit eine Funktion schreiben möchten, die eine variable Anzahl von Argumenten akzeptiert und diese generisch typisiert, müssen Sie für jede Anzahl von Argumenten, die dieser Funktion möglicherweise übergeben werden können, eine generische Überladung schreiben. Der Vorschlag von ...T ermöglicht es uns im Wesentlichen, den Compiler dazu zu bringen, die Funktionsdefinitionen für uns automatisch zu generieren.

Dein Angebot:

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

Erzwingt, dass alle Parameter als derselbe Typ behandelt werden, und sie können keine spezifischere Typprüfung durchführen. In meinem obigen Beispiel wären beispielsweise alle zurückgegebenen Werte vom Typ any .

Ich finde auch, dass das zusätzliche ... sehr schwer zu lesen ist.
Wie die Idee von Folgendes zu tun:

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

Das erste, was einem beim Lesen von apply<...T, in den Sinn kommt, ist, dass es sich um einen Spread-Operator handelt, der jedoch überhaupt nicht verbreitet wird.

@Griffork , in Ihrem Beispiel wäre T immer noch [string, int] .
Das meint @felixfbecker mit "statt dem TS-Compiler zu ermöglichen, einen Tupeltyp für T

Erzwingt, dass alle Parameter als derselbe Typ behandelt werden, und sie können keine spezifischere Typprüfung durchführen. In meinem obigen Beispiel wären beispielsweise alle zurückgegebenen Werte vom Typ any.

@Griffork Nein, meiner Meinung nach würde es einen args ableiten und jedem Parameter einen eigenen Typ durch seine Position im Tupel geben. ...args: T[] würde erzwingen, dass alle den gleichen Typ haben T , aber ...args: T (was derzeit ein Kompilierungsfehler ist) würde einen Tupeltyp für T ableiten.

Das erste, was einem beim Lesen von apply<...T in den Sinn kommt, ist, dass es sich um einen Spread-Operator handelt, der jedoch überhaupt nicht verteilt wird.

@unional stimme zu, genau das ist der

@unional
Ich habe es auch als Spread-Operator gelesen, ich habe es als "Diesen Typ bei jeder Verwendung verbreiten" gelesen.
Für mich, wenn ich das lese

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

Ich würde erwarten, dass T ein Array von etwas ist (zB string[] ).

Und das lesen:

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

Ich würde erwarten, dass alle Argumente dem Typ T zuweisbar sind (der ein Typ ist, z. B. string ).

Der obige Vorschlag zielte darauf ab , Generika nicht

@felixfbecker
Bearbeiten:
Oh, ok. Denke immer noch nicht, dass das intuitiv ist.

Ich würde erwarten, dass T ein Array von etwas ist (zB string[]).

Ein Tupel ist "irgendwas Array", es ist nur ein Array mit einer festen Länge und spezifischen Typen für jedes Element, zB [string, number] (vs. (string | number)[] , das ungebunden ist und nicht deklariert, welches Element was hat Typ).

Nun, was tippst du dann, wenn du dieses Verhalten tatsächlich willst?

Ich bin mir nicht sicher, auf welches Verhalten Sie sich genau beziehen, aber ich gehe davon aus, dass "alle Parameter denselben Typ haben müssen", was durch ...args: T[] .

Ich habe es auch als Spread-Operator gelesen, ich habe es als "Diesen Typ bei jeder Verwendung verbreiten" gelesen.

Deswegen finde ich es verwirrend.
Wenn Sie verbreiten, tun Sie es einfach, Sie deklarieren nichts, was "ausbreitbar" ist:

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

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

Ja. Wenn Sie deklarieren möchten, dass ein generisches Typargument "spreizbar" sein muss (was laut ES-Spezifikation nur bedeutet, dass es iterierbar sein muss), haben wir bereits eine Möglichkeit, dies in TypeScript mit extends auszudrücken:

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

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

Im Fall eines Restparameters ist natürlich bekannt, dass er nicht nur iterierbar ist, sondern ein Array.

Was, was wir sagen, wurde bereits vorgeschlagen: https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189703556

Aber der Rest ist zu lang, um in einer Sitzgelegenheit zu konsumieren. 🌷

Wenn die Verkettung von Tupeln landet, können wir Kirchennummern implementieren! Hurra!

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

Und wenn die Rekursion des bedingten Typs funktioniert, können wir ein Tupel mit der gewünschten Länge erstellen:

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

Ich gehe nicht davon aus, dass ich diesen Thread vollständig gelesen habe, aber wenn ...T in generischen Parametern verwirrend ist,
Warum nicht versuchen, die Destrukturierungssyntax auf Wertebene weiter zu spiegeln, so dass die Verwendung von [...T] in a
Argumentposition auf Typebene destrukturiert den Typ, wenn es sich um ein Tupel auf Typebene handelt? Dies würde auch erfordern
erlaubt die Eingabe von Rest-Parametern mit Tupeln, was das folgende Äquivalent an der Aufrufstelle ergeben würde
im TypeScript:

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

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

INB4 "aber das ist typgesteuertes Emittieren" – nein. Das ändert nichts, emittieren, das first immer noch zwei
unterschiedliche Argumente ausgegeben werden, wird second immer noch ein Rest-Argument haben. Das einzige was sich ändert ist
dass TypeScript auf der Aufrufseite prüft, ob die Parameter von second Reihe nach mit dem Tupel übereinstimmen
[number, string] .

Angenommen, wir geben die Syntax von [...Type] , dann könnten wir apply wie folgt schreiben:

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`

Die [...Type] Syntax würde sich vollständig wie eine Destrukturierung
und Verknüpfen von Tupeln auf Typebene nach Bedarf:

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

Ein Beispiel für die Eingabe der Funktion curry mit diesem:

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`

Ich gehe davon aus, dass es auch hilfreich wäre, sagen zu können, dass ein Typargument ein Tupel auf Typebene ist
irgendeiner Art. Die Frage wäre dann, wie – wenn man bedenkt, dass die Tupelzuordnung seit 2.7 (glaube ich?)
Berücksichtigung der Tupellänge – um das Konzept von _jedem Tupel auf Typebene_ auszudrücken. Aber vielleicht sowas wie
[...] könnte funktionieren? Ich habe keine starke Meinung, aber es wäre gut, wenn das Konzept benennbar wäre.

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

In diesem Fall könnte die obige Syntax von [...ArgsT] im Grunde eine Abkürzung für ArgsT extends [...] ,
die Verwendung der Destrukturierung auf Typebene impliziert eine Einschränkung für den Typ, um ein Tupel auf Typebene zu sein.

Die Gedanken?

@jaen :

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

Ja, das sieht aus wie #4130. Ich habe etwas bei #18004 versucht, aber mein Ansatz war ein bisschen hackig (synthetische Knoten).

Beim Ausdrücken eines Tupels hatte ich jemanden gesehen, der any[] & { 0: any } , was meiner Meinung nach funktioniert, bis ein leeres Tupel fwiw Typ ist. Ich persönlich habe mich nicht viel darum gekümmert, meistens habe ich mich nur mit any[] .

RxJS braucht das überall. Am kritischsten für Observable.prototype.pipe , für die wir derzeit viele Überladungen haben, aber ich werde immer gebeten, "nur noch eine Ebene" hinzuzufügen.

Als zweites @benlesh verwenden wir RXJS ausgiebig und benötigen dies für Pipe-Funktionen.

Ich bin der Autor von ppipe , das wie die Pipe-Funktionen in RXJS dies benötigt. Ich glaube, ich sehe hier ein Muster^^

Ich bin der Autor von runtypes , und diese Funktion wird dringend benötigt, um Vereinigungen und Schnittmengen auszudrücken. Die einzige (unvollständige) Problemumgehung sind gigantische Überladungen:

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

🤢

Ich hatte die Typen von ramda umgeschrieben, die dies auch für zB pipe , Linsen und Currying benötigen.
Wir brauchten Codegen, da das Currying das direkte Aufrechterhalten von Überlastungen unüberschaubar machte. Unser Typ path erstreckte sich über tausend Zeilen, und an diesem Punkt stellten wir fest, dass die Leistung des Überladungstyps ebenfalls problematisch wurde.

Ist das Problem mit dem Ableiten und Anwenden von Ruheargumenten gelöst?

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

Wenn der Typ von T in apply(example, ['0', 1, 2, 3]) als [string, number[]] , würde der Aufruf von apply einen Fehler auslösen.

Das heißt, der Typ von T ist wirklich

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

oder

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

In der Tat ein seltsames Biest, aber wenn man bedenkt, wie es ({0: string} & Array<number>)[0] wird
wird derzeit in string [1] es scheint möglich zu sein, ohne große Änderungen zu codieren
zum Typensystem.

[1] Ist das ein Fehler, sollte es wirklich string | number ?

Es tut uns leid, 36 Teilnehmer dieser Ausgabe zu stören (Tipp: Verwenden Sie dies ), aber wie können wir überwachen, ob dies noch in Betracht gezogen wird, ob dies auf der Roadmap steht usw.?

Schade, dass ihm nach 2einhalb Jahren noch niemand zugewiesen wurde, scheint ein ziemlich wichtiges Feature zu sein :(

PS: Ich habe ein paar Dutzend Kommentare gelesen, Cmd + F usw. ausprobiert, diese Informationen nicht gefunden.

@brunolemos es gibt einen Hinweis auf variadische Typen im neuesten Designmeeting
https://github.com/Microsoft/TypeScript/issues/23045
Um diese Funktion zu nutzen, müssen sie zuerst primitivere Konzepte und Konzepte iterativ erstellen, und wenn es genügend Grundlagen gibt, bin ich sicher, dass sie es zu jedem Meilenstein hinzufügen werden

Ich kann das nicht tun

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

Es handelt sich um #14174 aber als Relation

@kgtkr als Referenz siehe @fightingcats Trick , um den Compiler dazu zu bringen, die Rekursion zu übersehen.

Danke

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

Hm, ich habe eine Frage. Ich habe einen Code wie diesen, um mit Mixins umzugehen:

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

Ich benutze es wie folgt:

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

Es funktioniert wie erwartet - mit richtiger Eingabe, Zustandsbehandlung und so weiter, aber gibt es eine Möglichkeit, kürzer umzuschreiben, ohne auf eine variadische Art zu warten? Weil ich mich gerade ein bisschen blöd fühle.

Mit 3.0 ist es jetzt möglich, Rest-Argumente als Tupel zu deklarieren

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

Aber ist es möglich, umgekehrt zu erhalten, um Argumente vom Typ Tupel der gegebenen Funktion zu erhalten?

Etwas wie Arguments<foo> wäre schön.

@whitecolor wie wäre es damit?

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

mit TS 3.0 können wir das jetzt tun

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

aber wir haben ein kleines Problem, wir müssen Funktionen deklarieren, die ein Tupel-Ereignis für einzelne Parameter zurückgeben und auch irgendwie mit void umgehen, und wir müssen den Rückgabetyp deklarieren, sonst wird es als Array abgeleitet :)

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% 20return%20%5Ba1%2C%20a2%5D%3B%0D%0A%7D%0D%0A%0D%0Afunction%20foo21(a1%3A%20string%2C%20a2%3A%20boolean)%3A%20%5Bstring %5D%20%7B%0D%0A%20%20return%20%5Ba1%5D%3B%0D%0A%7D%0D%0A%0D%0A%0D%0Afunction%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%20compose%3CX%20extends%20any%5B%5D%2C%20Y%20extends%20any%5B%5D%2C%20Z%20extends%20any%5B%5D%3E( %0D%0A%20%20f%3A%20(... 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%20compose(%0D%0A %20%20foo0%2C%0D%0A%20%20bar0%0D%0A)%3B%0D%0A%0D%0Aconst%20baz21%20%3D%20compose(%0D%0A%20%20foo21%2C%0D %0A%20%20bar1%0D%0A)%3B%0D%0Aconst%20baz2%20%3D%20compose(%0D%0A%20%20foo2%2C%0D%0A%20%20bar2%0D%0A)% 3B%0D%0A%0D%0A%0D%0Aalert(baz2('a'%2C%20false))%0D%0Aalert(baz21('a'%2C%20true))%0D%0Aalert(baz0())

@maciejw
Verwenden Sie bedingte Typen:

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

sicher, aber das ist irgendwie hackig mit diesen as any :) Ich denke, es wäre cool, wenn das Typsystem dies ohne Hacks unterstützt

Nun, die Typen im Funktionsrumpf können mehrere Dinge sein, es gibt nicht wirklich viel, was man dagegen tun kann – Sie könnten stattdessen etwas wie (f as (...args: any[]) => Y) tun, aber ich denke, das reduziert die Übersichtlichkeit ohne wirklichen Grund

Wenn variadic-Typen zum Tragen kommen, könnte ich etwas TypeScript-Code, den ich für mein eigenes Projekt geschrieben habe, verallgemeinern, mit dem ich die Form meiner REST-API vollständig definieren und diese Form für die Typen des entsprechenden Node-Servers und JavaScript erzwingen kann Client-Bibliothek dafür.

Dies zu verallgemeinern würde es mir ermöglichen, meinen eigenen Code zu vereinfachen, dasselbe für beliebige APIs zu tun und weiter zu gehen und wahrscheinlich auch Swagger-Definitionen für andere Sprachclients zu generieren ... könnte für andere nützlich sein! Aber nur laut träumen, haha

Es ist mir gelungen, Pipe zu tippen
https://github.com/kgtkr/typepark/blob/master/src/pipe.ts

@kgtkr : Das sieht toll aus! :)

Der Pipe-Typ stürzt TS-Spielplatz für mich ab (obwohl andere gut funktionieren), ich denke, er braucht den neuesten TS?

TS zeigt auch einige Rekursionstiefenfehler - sieht so aus, als hätte @isiahmeadows #26980 dafür geöffnet.

@tycho01

Der Pipe-Typ stürzt TS-Spielplatz für mich ab (obwohl andere gut funktionieren), ich denke, er braucht den neuesten TS?

Es hängt auch für mich, und ich musste mich in die Devtools hacken, um es zu zwingen, einen Fehler auszulösen, um es auszubrechen.

TS zeigt auch einige Rekursionstiefenfehler - sieht so aus, als hätte @isiahmeadows #26980 dafür geöffnet.

Das ist für etwas verwandtes, aber anderes: Aufheben einer Einschränkung mit bedingten Typen aus zwei Gründen:

  • Um es einfacher zu machen, komplexere Arbeiten wie Listeniterationen durchzuführen. Es würde auch den Rahmen schaffen, um Dinge wie Integer-Mathematik auf Typebene zu öffnen, ohne den Compiler zum Absturz zu bringen oder in einem Ad-hoc-Durcheinander zu landen.
  • Um das Problem der Turing-Vollständigkeit des Typsystems von TS besser anzugehen, kann es möglicherweise entweder mit Tools, die es nutzen, verifiziert oder entfernt werden, indem im weiteren Verlauf eine nachweisbare Beendigung erzwungen wird.

Wenn das Entfernen indizierter Typen nicht ausreicht, um das Typsystem nicht wie es ist Turing-vollständig zu machen, können Sie dort einen Kommentar hinterlassen, damit ich ihn entsprechend aktualisieren kann. (Ich schlage natürlich nicht vor, es zu entfernen. Ich schlage nur vor, es intern besser zu handhaben, um die Leute vor möglichen Endlosschleifen zu warnen.)

Beim zweiten Nachdenken fühlt sich dieser Pipe-Typ wie eine wirklich komplizierte Art an, (vs...: Params<T[0]>) => ReturnType<Last<T>> tun. Jede darüber hinausgehende Iteration (abgesehen von zwischenzeitlichen Parameterüberprüfungen) wird wahrscheinlich bei eingabeabhängigen Rückgabetypen nützlicher.

@tycho01 Es wird versucht, Dinge wie diese einzugeben , wobei der Typ im Wesentlichen so ist:

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

Sie müssen die Parameter einzeln durchlaufen, um sie korrekt einzugeben, da die Parameter der nachfolgenden Parameter von den Rückgabewerten der vorherigen Parameter abhängig sind, und Sie müssen auch den ersten Parameter und den Endrückgabewert berücksichtigen (was


Wenn Sie sich meines in #26980 ansehen, ist es etwas klarer, was es tun soll (weniger Nummern jagen), und das ist ein Teil des Grundes, warum ich diese Feature-Anfrage gestellt habe.

@tycho01 @kgtkr Übrigens , ich habe diesen Fehler mit einem korrigierten PipeFunc Schnipsel aktualisiert, der der Einfachheit halber hierher kopiert wurde:

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

Dieser stürzt übrigens nicht auf dem Spielplatz ab. Es führt tatsächlich eine Typprüfung durch und dies geschieht ziemlich schnell.

Ich habe es noch nicht an einem möglichen _.flow oder _.flowRight Typ getestet, aber das sollte als Ausgangspunkt funktionieren.

@tycho01
erforderlich
typescript@next
3.0.1/3.0.2 funktioniert nicht

Dank dieser Thread, ich habe diese

Leute, bitte hört auf, Informationen zu posten, die für die Diskussion dieses Themas kaum relevant sind. Es gibt viele Leute, die dieser Diskussion folgen, weil wir verschiedene Arten wollen. Ich habe in den letzten Tagen mehr als 10 E-Mails erhalten, die für die Gründe, aus denen ich dieses Problem verfolge, irrelevant sind.
Ich gehe davon aus, dass andere mir zustimmen. Bisher habe ich nur gehofft, dass es aufhört, weil ich nicht zum Spam beitragen wollte. Aber im Ernst, genug ist genug.
PS Entschuldigung für diese Benachrichtigung, an alle, die sie so satt haben wie ich

@Yuudaari Ich werde darauf hinweisen, dass die Eingabe von Lodashs _.flow , Ramdas _.compose usw. eines der treibenden Dinge für diesen Fehler ist und eine erfolgreiche Eingabe Teil der Lösung dieses Problems ist. Tatsächlich ist dies einer der Gründe, die in der ursprünglichen Problembeschreibung aufgeführt sind.

Im Moment bin ich der Meinung, dass 99% der Probleme, die heute mit Variadics bestehen, in der Ergonomie liegen, nicht in der Funktionalität. Wir können Function.prototype.bind und Promise.all perfekt mit einer Mischung aus indizierten Typen, bedingten Typen und Rekursion eingeben (Sie können eine wiederholte Append Iteration der Liste für Function.prototype.bind , und Promise.all wäre eine einfache Iteration + Append ), nur ist es sehr umständlich und bösartig, dies zu tun.

Ich versuche nicht, den Lärm hier zu verstärken, sondern erkläre nur, dass die Dinge, die hier beginnen

Ich denke, die Leute, die hier auf Ankündigungen warten, haben die großen Neuigkeiten verpasst - es stellte sich heraus, dass die jetzt möglichen Concat<T, U> genau wie [...T, ...U] funktionieren.

Im Pipe geht es darum, die Funktionalität zu demonstrieren, nach der wir hier gefragt haben. Es geht darum, heute den Punkt dieses Threads zu erreichen.

Ich denke, das bedeutet, dass wir nicht schlechter dran wären, diesen Thread zu schließen, also ist dies vielleicht ein guter Moment, um zu fragen – was wollen die Leute noch von diesem Vorschlag?

[es ist] nur es ist sehr umständlich und bösartig

Die meisten Typen, die dies verwenden, verwenden selbst Rekursion, sodass diejenigen, die sie schreiben, definitiv damit vertraut sind, während Endbenutzer wahrscheinlich nur Bibliotheken mit vordefinierten Typen verwenden und ihr Frontend schreiben, ohne wissen zu müssen, dass TS-Iteration existiert.

An diesem Punkt kann dieser Vorschlag vielleicht die Leistung hauptsächlich verbessern?

Erstens, ist die Verwendung von Kartenobjekten, um das Typsystem zur Rekursion zu verleiten, überhaupt beabsichtigt? Es kommt mir ziemlich hackig vor. Wenn ich eine solche Funktionalität verwende (ich bin es, aber das ist irrelevant), wird sie später nicht kaputt gehen?

Zweitens ist die Verwendung dieser Problemumgehungen einfach nicht... freundlich. Es ist nicht sehr lesbar (besonders für diejenigen, die es nicht geschrieben haben), und daher sieht es miserabel aus, es zu pflegen.

Warum sollte ich auf einen Vorschlag zurückgreifen, der die gleichen Funktionen auf beabsichtigte, lesbare und wartbare Weise hinzufügt, nur weil es eine Problemumgehung dafür gibt?

Ich glaube nicht, dass die Existenz dieser Problemumgehung dazu führt, dass dieser Vorschlag als syntaktischer Zucker betrachtet wird, aber selbst wenn, warum sollte ich keinen syntaktischen Zucker für dieses Durcheinander haben?

@Yuudaari

Bearbeiten: Link für Kontext hinzufügen.

Erstens, ist die Verwendung von Kartenobjekten, um das Typsystem zur Rekursion zu verleiten, überhaupt beabsichtigt? Es kommt mir ziemlich hackig vor. Wenn ich eine solche Funktionalität verwende (ich bin es, aber das ist irrelevant), wird sie später nicht kaputt gehen?

Schauen Sie sich den Fehler an, den ich kürzlich eingereicht habe: #26980. Sie sind nicht der Einzige, der das Muster hinterfragt. Es ist hier ein bisschen off-topic, aber Sie können sich gerne dort einmischen.

Beachten Sie, dass ein wenig Mathematik erforderlich ist, um herauszufinden, ob etwas rekursives terminiert (einer der Hauptgründe, warum es überhaupt so nuanciert ist).

Zweitens ist die Verwendung dieser Problemumgehungen einfach nicht... freundlich. Es ist nicht sehr lesbar (besonders für diejenigen, die es nicht geschrieben haben), und daher sieht es miserabel aus, es zu pflegen.

Warum sollte ich auf einen Vorschlag zurückgreifen, der die gleichen Funktionen auf beabsichtigte, lesbare und wartbare Weise hinzufügt, nur weil es eine Problemumgehung dafür gibt?

Ich glaube nicht, dass die Existenz dieser Problemumgehung dazu führt, dass dieser Vorschlag als syntaktischer Zucker betrachtet wird, aber selbst wenn, warum sollte ich keinen syntaktischen Zucker für dieses Durcheinander haben?

Es gibt eine vereinfachte Möglichkeit, Tupel im üblichen Fall von Array.prototype.map zu iterieren , aber das war im Grunde genommen nutzlos für meine Bedürfnisse (ich brauchte einen Akkumulator).

Ich persönlich hätte gerne syntaktischen Zucker für diese:

  1. Verketten zweier Listen über [...First, ...Second] .
  2. Anhängen von Werten über [...Values, Item] .
  3. Extrahieren des letzten Elements über T extends [...any[], infer Last] .
  4. Extrahieren des Schwanzes über T extends [A, B, ...infer Tail] .

Kombinieren Sie das mit # 26980, und ich könnte die obigen Typen in diese umwandeln:

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;

Aber das war es schon. Ich sehe für keinen anderen syntaktischen Zucker viel Verwendung, da sich hier fast alles nur mit Tupeln befasst und Objekte bereits weitgehend alles haben, was Sie jemals für ähnliche Operationen benötigen würden.

Erstens, ist die Verwendung von Kartenobjekten, um das Typsystem zur Rekursion zu verleiten, überhaupt beabsichtigt? Es kommt mir ziemlich hackig vor. Wenn ich eine solche Funktionalität verwende (ich bin es, aber das ist irrelevant), wird sie später nicht kaputt gehen?

Ich denke, das offizielle Wort ist so etwas wie "Tu es nicht". @ahejlsberg sagte :

Es ist clever, aber es treibt die Dinge definitiv weit über ihren vorgesehenen Verwendungszweck hinaus. Während es für kleine Beispiele funktionieren mag, wird es schrecklich skalieren. Das Auflösen dieser zutiefst rekursiven Typen kostet viel Zeit und Ressourcen und könnte in Zukunft mit den Rekursionsreglern, die wir im Checker haben, in Konflikt geraten.

Tu es nicht!

@jcalz Also ein Grund mehr für #26980 zu existieren?

Als ich in der Pause dieses Jahres anfing, TS zu benutzen, war meine Neigung, _nur das_ zu schreiben! ( ...T ) in der Hoffnung, dass es die Syntax für variadische Typvar-Tupel wäre. Hoffe das kommt rein :)

Gerade eine neue Verwendung für [...T, ...U] : korrekte Eingabe von HTML-Buildern. Für ein konkretes Beispiel müssen die Kinder von <video> die folgenden sein:

  • Wenn das Element ein src Attribut hat:

    • Null oder mehr <track> Elemente

  • Wenn das Element kein src Attribut hat:

    • Null oder mehr <source> Elemente

  • Null oder mehr Elemente gemäß dem Inhaltsmodell des übergeordneten Elements, außer dass kein audio oder video Nachkommenselement zulässig ist.

Dies entspricht im Grunde diesem Typ, aber es gibt heute keine Möglichkeit, dies in TypeScript auszudrücken:

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

3,5 Jahre :/

Anwendungsfall:

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

Ich sehe Überlastungen, die im Abschnitt mit offenen Fragen des Vorschlags nur kurz erwähnt werden, aber definitiv etwas, auf das ich gestoßen bin und das hervorragend wäre, um eine Lösung oder einen Vorschlag zu sehen, z.

  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

derzeit funktioniert dies überhaupt nicht und gibt nur promisify als (ctx: ICtx, filter: FilterQuery<TSchema>) => Promise<TSchema[]> , wodurch Informationen aus diesen Signaturen verloren gehen.

AFAICT die einzige wirkliche Lösung dafür besteht im Grunde darin, eine Promise-Funktion zu erstellen und alle möglichen Typen für diese Variante manuell anzugeben. aber es gibt keine Möglichkeit, dem Aufrufer mitzuteilen, welche der Rückgabetypen er basierend auf den übergebenen Argumenten erwarten sollte, wenn Sie die Signatur auf diese Weise angeben.

Dies wird durch die Tatsache verschärft, dass Restparameter nur der letzte Parameter sein können (dh (cb, ...args) ist gültig, aber nicht (...args, cb) die Dinge richtig verteilen - zB wäre es ziemlich einfach, wenn cb immer das erste Argument wäre, um promisify als function promisify<T, V extends any[]>(fn: (cb: (err: Error | null, res?: T) => void, ...args: V)): (...args: V) => T einzugeben und Sie könnten zumindest Unionstypen für Signaturen mit derselben Antwort erhalten, aber weil es ist der letzte Parameter, da man hier nicht viel machen kann

@Qix- Ihr Szenario wird durch #24897 aktiviert. Wurde in TS 3.0 implementiert.

@ahejlsberg Uff! Super, danke ️

Es hat lange gedauert... Aber es ist heute möglich, variadische Arten zu schreiben. TS ist ausgereift genug, um komplexe Typen zu schreiben, die funktionieren. Also habe ich mir die Zeit genommen, Typen für Curry, Concat , Compose und Pipe für Ramda zu schreiben.

Und werden jetzt mit dem ts-toolbelt ausgeliefert .

Dieser Vorschlag ist jedoch ein netter syntaktischer Zucker, um allgemeine Tupelmanipulationen viel einfacher zu machen.

hast du es schon auf medium.com? URL?

Es gibt den Originalartikel auf Medium, aber der Bonus ist nicht darin enthalten, sondern im Repo. Es erklärt auch, wie ich all die kleinen Werkzeuge erstellt habe, um zu komponieren, zu pfeifen und zu curry :smile:

@pirix-gh, aber das sind keine variadischen Generika wie in diesem Vorschlag

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

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

@goodmind Ja, ist es nicht, es wird eher emuliert. Sie könnten also das ... wie folgt emulieren:

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

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

Ist das gleiche wie:

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

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

In der Zwischenzeit, während Sie auf diesen Vorschlag warten :hourglass_flowing_sand:

@pirix-gh kannst du bitte bei der Verpackungsfunktion helfen wie

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

Ich habe versucht, den Compose-Ansatz zu verwenden, ist aber gescheitert. Können Sie bitte einen geeigneten Weg vorschlagen?

Dies geschieht, weil TS beim Extrahieren der Parameter/der Rückgabe von fn die von Generika abhängig sind, sie auf ihren nächsten Typ ableitet (in diesem Fall ist T any ). Das ist im Moment also nicht möglich. Unsere beste Hoffnung ist es, darauf zu warten, dass dieser Vorschlag mit https://github.com/Microsoft/TypeScript/pull/30215 kombiniert wird

Oder vielleicht könnten wir einen Weg finden, Generika so zu erhalten/zu verschieben, dass wir Folgendes tun könnten:

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

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

Auf diese Weise würden wir fn aus Teilen davon rekonstruieren. Der fehlende Teil heute ist der generische Teil, wie @goodmind darauf hingewiesen hat.

@pirix-gh Wenn ich mich nicht irre, kannst du einfach dies tun, um das zu erreichen, was du da oben hast:

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 Nicht genau, denn das, was ich vorgeschlagen habe, funktioniert für eine unbegrenzte Anzahl von Argumenten. Aber vielleicht könnten wir das auch mit dem machen, was Sie vorgeschlagen haben (ich habe es im Vorschlag nicht gesehen):

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 Die Argumente vom Typ A , B und C in Ihrem Beispiel werden nicht verwendet.

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

Selbst wenn variadische Typen implementiert würden, würden die vorherigen beiden Beispiele wahrscheinlich einen Kompilierungsfehler erzeugen, was ist der Sinn von variadischen Typen, wenn Sie nur drei Argumente benötigen.

Ihre Beispiele sollten zeigen, warum variadic benötigt wird, wenn sie mit vorhandenem TS-Code durchgeführt werden können, hilft es der Ursache überhaupt nicht.

@goodmind Ja, ist es nicht, es wird eher emuliert. Sie könnten also das ... wie folgt emulieren:

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

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

Ist das gleiche wie:

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

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

In der Zwischenzeit, während ich auf diesen Vorschlag warte ⏳

Woher hast du die Concat<> ?

Edit: Egal, den Quellcode gefunden.

@pirix-gh also habe ich versucht, dies mit Ihren Vorschlägen zu tun, konnte es aber nicht herausfinden.

~Das Problem ist, dass ich versuche, die Parameter des ctor einer Klasse zu erweitern, und es funktioniert so weit, dass ich ein Array von Typen habe, aber ich kann sie nicht für die ctor-Parameter verteilen.~

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

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

Update: Egal, das hat auch mit einem Spread in der ctor-Funktion funktioniert.

Hier der Link zur Lösung

Das einzige Problem ist, dass TS fast jedes Mal abstürzt :|
und das sagt TypeScript Type instantiation is excessively deep and possibly infinite.ts(2589)

Aktualisierung 2:
Ich habe es erreicht, indem ich den neuen Typ an den Anfang gestellt habe, trotzdem wäre es schön, diese Typen zusammenführen zu können.

// ...
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');

im Gegensatz zu:

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

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

Link zur endgültigen Lösung.

Wenn ich das richtig verstehe, kann Object.assign wie folgt deklariert werden, um variadische Argumente vollständig zu unterstützen.

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

Gibt es einen Grund, warum es in lib.d.ts TypeScript anders deklariert wird?

Es verwendet keine rekursiven bedingten Typen, da diese nicht unterstützt werden (siehe # 26980 für eine Diskussion darüber oder dieser Kommentar , der uns sagt, dies nicht zu tun). Wenn man bereit ist, die aktuellen Schnittpunkt-Rückgabetypen zu verwenden, gibt es #28323.

@jcalz Ich habe einen schweren Test erstellt , der den Typ Minus in Aktion zeigt. Es führt die Minus 216000 Mal in weniger als 4 Sekunden aus. Dies zeigt, dass TS mit rekursiven Typen sehr gut umgehen kann. Aber das ist ziemlich neu.

Wieso den? Dies ist Anders :tada: (https://github.com/microsoft/TypeScript/pull/30769) zu verdanken. Er erlaubte mir, von bedingten Typen zu indizierten Bedingungen zu wechseln (wie ein Schalter). Und tatsächlich hat es die Leistung des ts-toolbelt um x6 verbessert. Vielen, vielen Dank an ihn.

Technisch gesehen könnten wir den Typ von @kimamula also sicher mit dem ts-toolbelt neu schreiben. Die Komplexität folgt 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'},
]>

Die lib macht auch die Rekursion mit Iteration sicher, das jeden Überlauf von TypeScript verhindert. Mit anderen Worten, wenn I 40 dann läuft es über und Pos<I> entspricht number . So stoppen Sie die Rekursion sicher.

Ein ähnlicher rekursiver Typ, den ich geschrieben habe ( Curry ) wird mit Ramda geliefert und scheint gut

Übrigens bedankte ich mich (@jcalz) auf der Seite des Projekts für all eure guten Ratschläge.

Ich bin mir nicht sicher, ob #5453 der beste Ort ist, um diese Diskussion zu führen ... sollten wir darüber in #26980 sprechen oder gibt es einen kanonischeren Ort? Auf jeden Fall würde ich gerne eine offizielle und unterstützte Möglichkeit haben , dies zu tun , die nicht möglicherweise auf zukünftige Versionen von Typoskript implodieren. Etwas, das in ihren Basistests enthalten ist, damit sie es reparieren, wenn es kaputt geht. Selbst wenn die Leistung als gut getestet wurde, wäre ich vorsichtig, dies in einer Produktionsumgebung ohne ein offizielles Wort von jemandem wie @ahejlsberg zu tun.

Etwas, das in ihren Basistests enthalten ist, damit sie es reparieren, wenn es kaputt geht.

Ich denke, wir haben intern etwas ziemlich enges verwendet

@weswigham verzeihen Sie mir, dass ich dicht bin, aber können Sie mir zeigen, wie der hervorgehobene Typ rekursiv ist? Was mir Sorgen macht, ist die Form

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

oder eine der Varianten, die ich gesehen habe. Wenn ich etwas vermisse und dies grünes Licht erhalten hat, lassen Sie es mich bitte wissen (und bringen Sie mir bei, wie man es benutzt!)

Oh, fair - in dieser Hinsicht ist es anders, ja. Ich sage nur das Muster "Objekte sofort indiziert, um Typen auszuwählen" und erkannte, dass wir _das_ hatten.

Ich habe eine Frage.

Wie viel von dem hier vorgeschlagenen Material ist noch relevant? Diese Ausgabe wurde vor 4 Jahren eröffnet und ich habe das Gefühl, dass sich seitdem einiges geändert hat.

Aus meinem Kommentar hier,
https://github.com/microsoft/TypeScript/issues/33778#issuecomment -537877613

Ich sagte,

TL;DR, Tupel-Typen, Rest-Argumente, zugeordnete Array-Typen, Tupel-Inferenz für Nicht-Rest-Argumente, rekursive Typ-Aliasnamen = keine wirkliche Notwendigkeit für die Unterstützung von Variablentyp-Argumenten

Aber ich bin gespannt, ob jemand einen Anwendungsfall hat, der mit den vorhandenen Tools einfach nicht aktiviert werden kann

Bis wir eine offiziell gesegnete Version von Concat<T extends any[], U extends any[]> ist dies immer noch relevant. Ich glaube nicht, dass uns das kommende Feature für

Haben wir nicht bereits Implementierungen von Concat<> ?

Oder ist das Stichwort hier "offiziell gesegnet"?

Denn meine Behauptung ist, dass man im Grunde alles (oder fast alles?) machen kann, was man sich im Moment wünschen kann, auch wenn es nicht ganz „offiziell gesegnet“ ist.

Aber ich denke, "offiziell gesegnet" sollte immer bevorzugt werden ... Guter Punkt. Ich bin zu daran gewöhnt, diese rekursiven Typaliase (ab) zu verwenden

Ich würde generell eine echte, elegante Syntax bevorzugen, damit ich nicht jedes Mal, wenn ich so etwas mache, meinen (oft jüngeren) Teamkollegen erklären muss, was für verwirrend spezifizierte Typen los ist, die der Status-Quo-Missbrauch erfordert. Diese Verwirrung schadet meiner Fähigkeit, TypeScript zu evangelisieren, oder zumindest diese Verwendungen davon in meiner Organisation.

Großes 👍 dazu!

Diese Funktion ist SO wichtig.

@AnyhowStep

Denn meine Behauptung ist, dass man im Grunde alles (oder fast alles?) machen kann, was man sich im Moment wünschen kann

Ich sehe keinen Beweis dafür, dass das Eingeben eines Spread-Parameters mit mehreren einzelnen Parametern daneben heute in TS leicht zu erreichen ist.

@matthew-dean stimmt nicht ganz. Hier ist ein Beispiel , das Sie bis zu einem gewissen Grad erreichen können.

Soweit ich weiß, versucht TS, so viele Vanilla-JS-Programme wie möglich zu schreiben. Hier ist ein Rätsel:

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

Ich würde erwarten, dass der Typ dort nicht komplexer ist als [number, ...T, number, ...T, number] . Wenn wir 20 Zeilen eines seltsamen Codes schreiben müssen, der einen Fehler in der letzten Zeile missbraucht, um einen richtigen Rückgabetyp in der letzten Zeile zu haben, wird dieses Problem nicht behoben.

@polkovnikov-ph Der Typ wird derzeit abgeleitet als: [number, ...any[]] , was nicht hilfreich ist.

Außerdem möchte ich anmerken, dass wir die zehnte Regel von Greenspun nicht 15 Jahre lang Head<> s und Cons<> für uns durchlaufen hat, und eine sehr praktische und saubere Variadic-Template-Syntax entwickelt. Wir können (Hunderte von Jahren) Entwicklerzeit sparen und nur die besten Teile daraus entnehmen.

Variadic-Typen haben beispielsweise in C++ eine andere Art , sodass Sie keine Variadic-Typvariablen verwenden können, wo der Typ erwartet wird, im Gegensatz zu einem Typ, der in TS extends any[] . Dies ermöglicht C++, Tupel zu mappen/zu zippen, indem eine Variable vom Typ variadic innerhalb eines Ausdrucks erwähnt wird, der in einen Ellipsenoperator eingeschlossen ist. Dies ist so ziemlich eine Tupel-Alternative von zugeordneten Objekttypen.

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]

Bitte erwähnen Sie, dass die vorgeschlagene Ellipsen-Syntax anstelle von extends any[] im Beispiel nicht nur aus ästhetischen Gründen verwendet wird, sondern weil

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

ist bereits ein gültiges TS-Programm. C ist any[] anstelle von [any[], any[]] , das der zugeordnete Variadic-Typ generieren würde.

@DanielRosenwasser Ich entschuldige mich dafür, dass ich dich so

Abgesehen davon wäre es für mein Team eine große Hilfe, nur Spread-Operationen auf Typebene für Tupeltypen zu haben, auch wenn das Fehlen von Variadic-Arten bedeutet, dass sie nicht mit Typparametern verwendet werden können. In unserem Problembereich sind "Arrays mit einer gewissen Struktur" sehr verbreitet. Es würde uns vieles vereinfachen, wenn diese Operation funktionieren würde:

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

@sethfowler Wenn Sie einige Beispiele dafür haben, was Sie ausdrücken möchten, ist das für uns immer hilfreich. Ansonsten interessiert Sie vielleicht https://github.com/microsoft/TypeScript/issues/26113

@DanielRosenwasser Klar, ich kann die Dinge etwas konkreter machen. Ich halte das Wesentliche aus der Sache heraus, aber im Großen und Ganzen können Sie sich unser Projekt so vorstellen, dass es einen Strom von Grafikoperationen und anderen ähnlichen Ereignissen generiert, die an einen Remote-Server gesendet werden. Aus Effizienzgründen müssen wir diese Operationen im Speicher in einem Format darstellen, das direkt in ihre serialisierte Form übersetzbar ist. Die Typen für diese Ereignisse sehen am Ende etwa so aus:

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

Wir würden diese Typen gerne wie folgt ausdrücken können:

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

Wir haben eine große Anzahl dieser Befehle, so dass nur die Verteilung auf Typebene zu dramatisch besser wartbarem Code führen würde. Variadische Arten zu haben, um Funktionen auf Typebene wie Repeated<> und RepeatedUpToTimes<> schreiben (die in diesem Beispiel zu rekursiv definierten Vereinigungen von Tupeltypen ausgewertet würden) würde die Dinge noch weiter vereinfachen.

Es wäre auch äußerst nützlich, Unterstützung für Dinge wie die typsichere Verkettung von Tupeltypen zu haben (wie im OP besprochen). Um die oben genannten Typen zu verwenden, müssen wir derzeit das gesamte Tupel in einem einzigen Tupelliteralausdruck konstruieren. Wir können es jetzt nicht in Teilen konstruieren und zusammenfügen. Mit anderen Worten, die folgenden Operationen funktionieren heute nicht, aber wir wünschen uns wirklich, dass sie es tun würden.

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

Hoffentlich sind diese Beispiele hilfreich!

Sie können ganz einfach einen Concat<> Typ schreiben und einen Concat3<> Typ erstellen, indem Sie Concat<> .

Dann,

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

Aus dem Obigen können Sie eine Concat-Funktion mit Überladungen für 2,3,4,5,6 usw. schreiben. Anzahl von Argumenten.

Es ist sogar möglich, eine Concat<>-Impl zu schreiben, die ein Tupel von Tupeln nimmt und die Tupel verkettet. Ein var-arg-Concat<>-Typ.


Es ist nichts, was heute nicht möglich ist. Dies ist möglich, auch wenn Sie einen rekursiven Typ und eine Hilfsfunktion schreiben müssen.

Jedes Mal, wenn ich diese rekursiven Typen in vscode verwende, will TS die CPU töten oder sie hängt einfach! Das ist das Hauptproblem, ich habe das Gefühl, dass TS ohne guten Grund zu schwer wird.

Vielleicht tun die Leute, die die Typen schreiben, nicht genug, um sie zu optimieren?

Ich möchte nicht spammen oder die alten Konversationen hineinziehen, die diesem Thread nicht viel Wert verleihen, aber ich erinnere mich, dass ich irgendwann gehört habe, dass die Berechnung rekursiver bedingter Typen in Javascript teuer ist (ich habe es hier in diesem Thread gefunden ).

Das heißt (ich weiß, es klingt vielleicht verrückt), vielleicht ist es an der Zeit, TS in einer anderen Sprache umzuschreiben, um dem Typsystem einen Schub zu geben, da TS bereits so stark gewachsen ist, dass es vernünftig ist, nach einem besseren zu fragen.

Sie können ganz einfach einen Concat<> Typ schreiben und einen Concat3<> Typ erstellen, indem Sie Concat<> .

Könnten Sie bitte eine Implementierung für den Concat<> Ihnen beschriebenen Typ Cons<> zu schreiben, aber Concat<> ist nicht so einfach (für mich) und ich würde gerne sehen, was Sie sich vorstellen.

In Bezug auf Concat3<> , Concat4<> usw. besteht die Hoffnung, dass wir auf lange Sicht nicht Dutzende solcher Varianten schreiben müssen, da wir variadische Arten haben werden. 🙂 Wenn aber heute eine gute Umsetzung möglich ist, wäre das eine sinnvolle Notlösung.

Für die regelmäßige Verkettung von zwei Tupeln,
https://github.com/AnyhowStep/ts-trampoline-test (verwendet Trampoline, um sehr große Tupel zu concat, die die meisten Leute nicht brauchen)

Concat3 wäre nur Concat, C>

Der VarArgConcat wäre,
VarArgConcat<TuplesT extends readonly (readonly unknown[])[], ResultT extends readonly unknown[] = []>

Solange das Tupel nicht leer ist, VargArgConcat<PopFront<TuplesT>, Concat<ResultT, TuplesT[0]>>

Wenn TuplesT leer ist, gib ResultT . zurück

Natürlich führt eine naive Rekursion zu maximalen Tiefenfehlern mit Tupeln einer angemessenen Länge. Verwenden Sie also entweder die Rekursionstechnik in ts-toolbelt oder verwenden Sie Trampoline mit Copy-Paste bis zur gewünschten Tiefe


Dieses Repo, mit dem ich verlinkt habe, verwendet Reverse<> , um Concat<> zu implementieren. Ich habe den Code aus einem anderen Projekt, an dem ich arbeite, kopiert und eingefügt.

Ich stimme zu, dass dies eine enorm nützliche Funktion wäre.

Nehmen wir an, wir haben einen Typ T :

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

Und wir wollen einen neuen Typ erstellen, mit einem zusätzlichen Tag "d" , das dem Tupel T["tags"] hinzugefügt wird. Benutzer könnten zunächst versuchen, dieses Dienstprogramm ( WithTag<NewTag, ApplyTo> ) wie folgt zu erstellen:

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

Bei diesem Versuch wird derzeit der Fehler A rest element type must be an array type . Benutzer könnten denken, dass der Austausch von string[] gegen Array<string> einen Unterschied macht, aber das ist nicht der Fall. Auch die Verwendung einer Bedingung + never :

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

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

Verwandte Probleme: :

Leider funktioniert diese Strategie nicht für Ruheparameter. Diese werden einfach in Arrays umgewandelt:

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

Hier, Curry: ...([string, boolean[]] | [boolean[]]) => number .
Ich denke, dies könnte unterstützt werden, wenn es einen Sonderfall für Funktionen mit einem Tupelrestparameter gäbe, bei dem das letzte Element des Tupels ein Array ist.
In diesem Fall würde der Funktionsaufruf zusätzliche Argumente des richtigen Typs zulassen, um dem Array zu entsprechen.
Das scheint jedoch zu komplex, um sich zu lohnen.

Dies hat zwei Probleme:

  1. Der Code ist falsch. curried() akzeptiert kein Array. Das Füllen des c Rest-Parameters mit [true, false] könnte durch curried('foo', ...[true, false]) aber dies wird in TypeScript mit diesem Vorschlag fehlschlagen. In einigen Fällen können wir möglicherweise keine Tipplösung anbieten, aber es wird davon abgeraten, jemanden falsch anzugeben!
  2. Sie haben unbeabsichtigt optionale und restliche Parameter kombiniert und einen Fehler in Ihrem Vorschlag aufgedeckt. curried() kann nicht ohne b aufgerufen werden, sondern mit c . Dies führt zu Fehlverhalten. TypeScript weiß, dass curried() (...items: [string, boolean[]] | [boolean[]]) aber das stimmt einfach nicht . Da JavaScript keine Eingaben macht, wird [true, false] an c (vorausgesetzt, wir haben das obige Problem gelöst) mit curried([true, false]) , b auf undefined (oder sein Standardwert) und c auf [true, false] , aber b auf true und c auf [false] !

Ich schlage folgende Korrekturen vor:

  1. Für das zweite (und einfachere) Problem ist die Lösung einfach: Ziehe kein Union-Tupel für das letzte optionale Argument (dh [number, string, boolean[]] | [number, boolean[]] in unserem Fall), wenn es einen Rest-Parameter gibt. Leiten Sie stattdessen [number, string, boolean[]] | [number] d. h. ein Fall für die vollständige Signatur, einschließlich aller Optionalen und Reste, einer für jeden optionalen außer dem last und einer ohne den letzten und den Rest.
  2. Das erste Problem ist schwieriger: Sie haben bereits gesagt, dass Sie es für zu komplex halten, um sich zu lohnen. Ich denke, es lohnt sich angesichts der Popularität von Ruheparametern, aber es ist _notwendig_ wegen des ersten Problems (Sieg! 😄). Ich denke, es wäre schön, wenn wir die Schnittstelle für tuple-with-last-rest-array verfügbar machen (ich denke an die Syntax [t1, t2, t3, ...arr] ), aber das müssen wir nicht. Wir können dabei als Intern bleiben (haha, du musst dich noch damit auseinandersetzen, wie man den Typ in einer IDE anzeigt 😈).

Aber nach allen Beschwerden und Provokationen, toller Vorschlag! Danke 👍 (nur um dich zu beruhigen, dies ist die erste Ausgabe in GitHub, auf die ich mit drei Emojis geantwortet habe - 👍, 🎉 und ❤️).

Dies wäre wirklich nützlich im Angular Injector, der derzeit any https://github.com/angular/angular/issues/37264

In diesem Beispiel könnten A, B, C als ein einzelner variadischer generischer Typ ...A dargestellt werden. Aber ich habe keine Ahnung, wie dies auf etwas abgebildet werden würde, bei dem jedes Element des variadischen Generikums in einen anderen Typ eingeschlossen wäre ( Type ). vielleicht mit einem Helfertyp? Oder sollte die Syntax etwas wie ...Type<A> zulassen?

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

(Kontext: Eine Implementierung von Provider würde Instanzen von deps in dieser Reihenfolge in diese Factory-Funktion injizieren. Eine strikte Typisierung würde sicherstellen, dass der Entwickler weiß, was in welcher Reihenfolge injiziert wird.)

Wenn dies erledigt ist, denken Sie bitte daran, den zweiten Parameter von String.prototype.replace zu aktualisieren, damit er endlich eine korrekte Typisierung in Typescript hat!

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

@Griffork Sie wissen, dass die Regex

Dies wäre sehr nützlich im Angular Injector, der derzeit any angle/angular#37264 verwenden muss

In diesem Beispiel könnten A, B, C als ein einzelner variadischer generischer Typ ...A dargestellt werden. Aber ich habe keine Ahnung, wie dies auf etwas abgebildet werden würde, bei dem jedes Element des variadischen Generikums in einen anderen Typ eingeschlossen wäre ( Type ). vielleicht mit einem Helfertyp? Oder sollte die Syntax etwas wie ...Type<A> zulassen?

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

(Kontext: Eine Implementierung von Provider würde Instanzen von deps in dieser Reihenfolge in diese Factory-Funktion injizieren. Eine strikte Typisierung würde sicherstellen, dass der Entwickler weiß, was in welcher Reihenfolge injiziert wird.)

@AlexAegis

Ich habe das Gefühl, dass es so geschrieben wird:

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

Dieses Problem wird jetzt von #39094 behoben, das für TS 4.0 vorgesehen ist.

Wenn das mit 4.0 kommt, haben wir jetzt einen Grund, es 4.0 zu nennen 😃
Das ist wirklich ein wichtiges neues Feature 🎉

Das ist toll! Nur "links" ist für Literal-String-Typen gleich

@sandersn Ich versuche darüber nachzudenken, wie diese Syntax in Dingen wie RxJS , wo die Parameter der Methode pipe irgendwie voneinander abhängig sind.

wie in pipe(map<T, V>(...), map<V, U>(...), filter(...), ...) . Wie würden Sie es auf eine Weise eingeben, die sie jetzt nicht tun? (Die Dutzende von Zeilen mit unterschiedlichen variadischen Längen eingeben)

@gioragutt mit der PR, die @ahejlsberg eingereicht hat. Ich denke, das würde funktionieren, aber ich könnte mich irren 😄

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 Funktioniert aufgrund eines kreisförmigen Typfehlers nicht ganz .

Der übliche Workaround funktioniert jedoch .

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 Das scheint bei mir nicht zu funktionieren. 😢
Spielplatz beispiel .

Ich habe etwas näher an der Arbeit, aber es wird nicht die Typen ableiten.
Spielplatz Beispiel

ich musste mich ändern
R extends readonly [unknown] ? 1 : 0
zu
R extends readonly [infer _, ...infer __] ? 1 : 0

Nicht sicher warum

@tylorr @treybrisbane Könnte verwandt sein: https://github.com/microsoft/TypeScript/pull/39094#issuecomment -645730082

Außerdem würde ich in jedem Fall dringend empfehlen, dies in der Pull-Anfrage zu teilen, in der dieser Kommentar enthalten ist.

Variadische Tupeltypen sind eine großartige Ergänzung der Sprache, danke für die Mühe!

Es scheint, dass Konstrukte wie curry auch davon profitieren könnten (gerade mit dem Staging-Playground getestet):

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

Die generische Funktion funktioniert jedoch nicht mit dem obigen Curry.

Ich glaube nicht, dass diese PR dieses spezielle Problem löst.

Mit der PR funktioniert das

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

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

Aber dieses Thema wünscht sich, soweit ich das sehe, eine Implementierung dafür:

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

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

Es gibt viele spitze Klammern, sieht ein wenig überflüssig aus.

@AlexAegis Ich bin mir nicht sicher, ob ich in

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

Denken Sie nicht, dass wir wirklich ein ganz neues Konzept (dh Parameter vom Typ Pause) wollen, nur damit die eckigen Klammern in den seltenen Fällen vermieden werden können, in denen Rückschlüsse nicht verstanden werden können.

@ahejlsberg Ich sehe. Ich habe gefragt, weil einige Bibliotheken (RxJS wie erwähnt) Problemumgehungen verwendet haben, um diese Funktionalität bereitzustellen. Aber es ist endlich.

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

Also bleiben sie jetzt entweder dabei oder lassen die Benutzer die Klammern eingeben, was eine bahnbrechende Änderung und nicht so intuitiv ist.

Der Grund, warum ich dieses Beispiel verwendet habe, ist, dass ich hier einfach den Typ dieses Tupels definiert habe. Eine eckige Klammer hier, eine da

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

Hier ist es nicht so offensichtlich, dass sich das Tupel auf diesen Rest-Parameter bezieht, wenn man es von außen betrachtet

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

Aber ja, wie Sie sagten, wenn wir die Inferenz herausfinden lassen, dann erfordern diese trivialen Fälle keine Änderung vom Benutzer. Aber wir wissen nicht, ob sie sie explizit festgelegt haben oder nicht, es liegt an ihnen, also zählt es immer noch als Breaking Change. Aber das ist die Sorge der Bibliothek und nicht die dieser Änderung.

Das heißt, ich finde es einfach seltsam, dass, wenn es Ruheparameter gibt, die von außen einzeln definiert werden und die ein einzelnes Array sind, das durch ... , nicht auf die gleiche Weise generisch gemacht werden können: eins nach dem anderen außen, einzelnes Array innen, differenziert durch ... .

Geringfügige Syntaxabweichungen sind die Supportkosten für einen separaten nicht wirklich wert
nett. Die Verwendung von Arten wäre eine richtige Designentscheidung, wenn TS plante
Unterstützung für Ruheparameter, aber ich denke, jetzt könnte es zu mehr führen
Verwirrung sowohl für Sprachentwickler als auch für Benutzer. Wir brauchten eine Lösung für
dieses Problem, und Anders hat seine Arbeit außergewöhnlich gut gemacht und das vermieden
Komplexität durch Festhalten an [...T] anstelle von T . Hut ab!

(Könnten wir uns jetzt einen Fehler ansehen, der den Kreuzungstyp zu
abgeleitete Variable im bedingten Typ gibt den Schnittpunkttyp ganz rechts zurück
Argument, oder ist die Union von Arrays bitte kein Array von Union? Wir immer noch
haben große Showstopper im Typsystem.)

Am Fr, 19. Juni 2020, 10:41 Uhr schrieb Győri Sándor [email protected] :

@ahejlsberg https://github.com/ahejlsberg Ich verstehe . Ich habe gefragt, weil
einige Bibliotheken (RxJS wie erwähnt) haben Problemumgehungen verwendet, um dies bereitzustellen
Funktionalität. Aber es ist endlich.

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

Also bleiben sie jetzt entweder dabei oder lassen die Benutzer die Klammern eingeben,
was nicht so intuitiv ist.

Der Grund, warum ich dieses Beispiel verwendet habe, ist, dass es hier einfach ist
dass ich den Typ dieses Tupels definiert habe. Eine eckige Klammer hier, eine da

foo<[Zahl, Zeichenfolge]>([12, '13']);

Hier ist es nicht so offensichtlich, dass sich das Tupel auf diesen Restparameter bezieht, wenn
du siehst es von außen an

foo<[Zahl, Zeichenfolge]>(12, '13');


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646490130 ,
oder abmelden
https://github.com/notifications/unsubscribe-auth/AAWYQIMTTB6JEPSQFUMTMDTRXMJD5ANCNFSM4BTBQ7DQ
.

Ich bin natürlich bei weitem nicht an seinem Kaliber, aber ich stimme
Meiner Erfahrung nach liegt ein Großteil der Komplexität von Typoskripten darin begründet, dass viele (sicherlich interessante und nützliche) Funktionen als ihre eigenen Konzepte in spezielle Formate eingebettet sind.

Diese Komplexität ist jedoch nicht von Natur aus eine Funktion der Anzahl der Funktionen!
Stattdessen könnte die Sprache um größere, übergreifendere Konzepte herum entworfen werden, aus denen dann diese Spezialfälle trivial abgeleitet oder in der std-Bibliothek (Typ) implementiert werden könnten.

Das allgemeinste solche Konzept wäre natürlich, abhängige Typen vollständig zu implementieren, aus denen dann alles andere abgeleitet werden könnte, aber so weit zu gehen ist nicht notwendig:
Wie C++ und in geringerem Maße auch Rust gezeigt haben, bieten Ihnen einige groß angelegte, konsistente Konzepte eine Menge kostenloser Funktionen.
Dies ähnelt dem, was OCaml und Haskell (und ich nehme an, F#?) auf der Wertebene getan haben, nur auf der Typebene.

Typ-Level-Programmierung ist nicht zu befürchten, solange sie in die Sprache integriert ist, anstatt spezifische Funktionen bereitzustellen.
Die Funktionen in C++ 14/17 sind sehr intuitiv, abgesehen von ihrer Syntax, die rein auf historisches Gepäck zurückzuführen ist.

Übergreifende Konzepte hätten im ursprünglichen Design hinzugefügt werden können. Nach dem Design
Fehler wurde bereits gemacht, Konsistenz kann nicht hinzugefügt werden, ohne ein großes Risiko einzugehen
Rückeninkompatibilität. Ich stimme dem Verdacht bezüglich der Sprachgestaltung zu, da
ein Ganzes (TS ist weit von den Standards der Wissenschaft entfernt, niemand kann es
bin damit nicht einverstanden). Es gibt viele Fehler und Inkonsistenzen, die sind
Grundlage für Millionen von Produktionscodebasen. Bloße Tatsache, dass
Entwickler können sich nützliche Ergänzungen zur Sprache einfallen lassen
ohne diese Fehler versehentlich zu beheben, ist meiner bescheidenen Meinung nach großartig
und verdient Respekt. TS hat hier die gleiche Designkomplexität wie C++, aber es ist
Ausdruckstypsystem verschlimmert die Situation.

Am Fr, 19. Juni 2020, 12:47 Uhr schrieb Bennett Piater [email protected] :

Ich bin natürlich bei weitem nicht an seinem Kaliber, aber ich stimme respektvoll nicht zu
mit @ahejlsberg https://github.com/ahejlsberg .
Meiner Erfahrung nach kommt ein Großteil der Komplexität des Typoskripts von derTatsache, dass viele (interessante und nützliche) Funktionen verfügbar sindals eigene Konzepte speziell eingefasst.

Diese Komplexität ist nicht von Natur aus eine Funktion der Anzahl der Funktionen
obwohl!
Stattdessen könnte die Sprache um größere, übergreifendere herum entworfen werden
Konzepte, aus denen sich diese Spezialfälle dann trivial ableiten lassen, oder
in der std (type)-Bibliothek implementiert.

Das allgemeinste solche Konzept wäre natürlich die vollständige Umsetzung
abhängige Typen, aus denen dann alles andere abgeleitet werden könnte, aber
so weit zu gehen ist nicht nötig:
Wie C++ und in geringerem Maße auch Rust gezeigt haben, haben einige große,
konsistente Konzepte bieten Ihnen eine Menge Funktionen kostenlos.
Dies ähnelt dem, was OCaml und Haskell (und ich nehme an, F#?)
die Wertebene, nur auf der Typebene.

Typ-Level-Programmierung ist nichts, wovor man Angst haben muss, solange es so ist
in die Sprache integriert, anstatt angehängt zu werden, um Spezifisches bereitzustellen
Merkmale.
Die Funktionen in C++ 14/17 sind bis auf ihre Syntax sehr intuitiv.
was rein auf historisches Gepäck zurückzuführen ist.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646543896 ,
oder abmelden
https://github.com/notifications/unsubscribe-auth/AAWYQIMWYLGGCWPTDBZJR4TRXMX4RANCNFSM4BTBQ7DQ
.

@polkovnikov-ph Ich bin froh, dass wir uns in der vorliegenden Frage einig sind :)

Was die Lösung angeht, denke ich, dass es immer noch eine Überlegung wert wäre, schrittweise zu einem sorgfältiger gestalteten Typensystem überzugehen. Hauptversionen sind schließlich eine Sache, und die Alternative besteht darin, im Cluster * * zu landen, das ist C++ 20 - ein bewundernswerter Versuch, noch schöner gestaltete Funktionen über 2 Schichten früherer Versuche hinzuzufügen, die nicht entfernt werden können, in a Syntax, die bereits nicht deterministisch geparst werden kann.

All dies gehört nicht zum Thema dieses Threads und wird hier diskutiert. Also versuche ich ehrlich zu sein:

Es dauerte Jahrzehnte, bis die Wissenschaft den richtigen Ansatz für die Subtypisierung herausgefunden hatte: Das mlsub-Typsystem wurde erst vor 6 Jahren erstellt, lange nachdem TypeScript zum ersten Mal veröffentlicht wurde. Es könnte die Grundlage für Klassen, Interfaces, Unions- und Schnittmengentypen mit übergreifenden Konzepten sein.

Aber denken Sie auch daran, dass es bedingte Typen gibt. Mir sind keine Papiere bekannt, die ihnen formale Semantik geben oder ein minimales Typsystem mit bedingten Typen mit Fortschritts- / Erhaltungsbeweisen beschreiben. Ich glaube, das könnte damit zu tun haben, dass Wissenschaftler immer noch schüchtern sind, ihre gescheiterten Versuche zu drucken. Wenn Ihr Vorschlag davon ausgeht, dass diese wichtigen inkompatiblen Versionen in den 2040er Jahren erstellt werden, wenn sich die Wissenschaft mit bedingten Typen vertraut macht, kann ich dem zustimmen.

Andernfalls müsste ein "sorgfältig entworfenes Typsystem" bedingte Typen aus der Sprache entfernen, und ich glaube nicht, dass irgendjemand der Aufgabe gewachsen ist, 60% von DefinitelyTyped zu konvertieren, um eine beliebige Alternative zu verwenden, um sie zu ersetzen. (Und dann tun Sie es noch mehrmals, denn es ist nicht das einzige Problem.)

Ich fürchte, die einzige praktikable Lösung besteht darin, eine separate Programmiersprache zu erstellen, die irgendwie TS ähnelt und die Entwickler irgendwie (nicht nur dadurch, dass das Schreiben von Code angenehmer ist) dazu bringt, sie zu verwenden. Ryan empfahl diesen Ansatz für die TS-Verbesserung zuvor ziemlich laut .

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

blendsdk picture blendsdk  ·  3Kommentare

siddjain picture siddjain  ·  3Kommentare

seanzer picture seanzer  ·  3Kommentare

weswigham picture weswigham  ·  3Kommentare

Antony-Jones picture Antony-Jones  ·  3Kommentare