Typescript: Vorschlag: Bereich als Nummerntyp

Erstellt am 30. Apr. 2017  ·  106Kommentare  ·  Quelle: microsoft/TypeScript

Bei der Definition eines Typs kann man mehrere Zahlen angeben, die durch | .

type TTerminalColors = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;

Erlauben Sie, Nummerntypen als Bereiche anzugeben, anstatt jede Nummer aufzulisten:

type TTerminalColors = 0..15;
type TRgbColorComponent = 0..255;
type TUInt = 0..4294967295;

Vielleicht verwenden Sie .. für Integer und ... für Floats.

interface Math {
  random(): 0...1
}
In Discussion Suggestion

Hilfreichster Kommentar

Diese Idee kann auf Zeichen erweitert werden, zB "b".."d" wäre "b" | "c" | "d" . Es wäre einfacher, Zeichensätze anzugeben.

Alle 106 Kommentare

Diese Idee kann auf Zeichen erweitert werden, zB "b".."d" wäre "b" | "c" | "d" . Es wäre einfacher, Zeichensätze anzugeben.

Ich denke, dies kann erweitert werden und Syntax wie und Semantik wie Haskell-Bereiche verwenden.

| Syntax | Entzuckert |
|--------------------------|---------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------|
| type U = (e1..e3) | type U = \| e1 \| e1+1 \| e1+2 \| ...e3 \|
Die Vereinigung ist never wenn e1 > e3 |
| type U2 = (e1, e2..e3) | type U2 = \| e1 \| e1+i \| e1+2i \| ...e3 \| ,
wobei das Inkrement, i , e2-e1 .

Wenn das Inkrement positiv oder null ist, endet die Vereinigung, wenn das nächste Element größer als e3 ;
die Vereinigung ist never wenn e1 > e3 .

Wenn das Inkrement negativ ist, wird die Vereinigung beendet, wenn das nächste Element kleiner als e3 ;
die Vereinigung ist never wenn e1 < e3 . |

@panuhorsmalahti Was ist, wenn Sie "bb".."dd" angeben?

@streamich

Vielleicht verwenden Sie .. für Integer und ... für Floats.

Ich mag die Idee, solche ganzzahligen Typen zu generieren, aber ich sehe nicht, wie Gleitkommawerte funktionieren könnten.

@aluanhaddad Sag Wahrscheinlichkeit:

type TProbability = 0.0...1.0;

@streamich also hat dieser Typ theoretisch unendlich viele mögliche Einwohner?

@aluanhaddad eigentlich wäre es in IEEE-Gleitkomma bei weitem nicht unendlich. Es hätte nach meinen Berechnungen 1.065.353.217 Einwohner.

0.0...1.0 ? JS verwendet IEEE Double, das sind 53 Bit Dynamikbereich. Wenn dies unterstützt werden sollte, müssten Bereiche ein erstklassiger Typ sein, und dies zu einer Vereinigung zu entzuckern, wäre mehr als unpraktisch.

@jcready in der Tat, aber wie @fatcerberus betont , wäre es unerschwinglich, ihn als

Was ich umständlich erreichen wollte, war, dass dies eine Vorstellung von diskreten vs. kontinuierlichen Typen in die Sprache einführen würde.

es als Gewerkschaftstyp zu erkennen, wäre unerschwinglich.

@aluanhaddad Ja, aber selbst die Angabe einer vorzeichenlosen Ganzzahl als Union wäre sehr teuer:

type TUInt = 0..4294967295;

Dies erfordert wirklich einige überzeugende Anwendungsfälle, da die Implementierung von Gewerkschaften heute völlig ungeeignet ist, um Gewerkschaften dieser Größe zu verwirklichen. Etwas, das passieren würde, wenn du so etwas schreibst

type UInt = 0..4294967295;
var x: UInt = ......;
if (x !== 4) {
  x;
}

wäre die Instanziierung des Unionstyps 0 | 1 | 2 | 3 | 5 | 6 | 7 | ... .

Vielleicht könnte es nur gegen Zahlenliterale funktionieren. Alle nicht-literalen Zahlenwerte müssten explizit mit größeren/kleineren-Vergleichen verfeinert werden, bevor sie als in dem Bereich vorkommend betrachtet werden. Integer-Bereiche würden auch eine zusätzliche Number.isInteger() Prüfung erfordern. Dies sollte die Notwendigkeit beseitigen, tatsächliche Unionstypen zu generieren.

@RyanCavanaugh Subtraktionstypen? 🌞

Negative Typen, Typnegation.

Alles andere als ein String:

type NotAString = !string;

Jede Zahl außer Null:

type NonZeroNumber = number & !0;

@streamich Subtraktionstypen werden von #4183 abgedeckt

Mein Anwendungsfall ist: Ich möchte einen Parameter als 0 oder eine positive Zahl eingeben (es ist ein Array-Index).

@RoyTinker Ich denke definitiv, dass das cool wäre, aber ich weiß nicht, ob dieser Anwendungsfall dem Argument hilft.
Ein Array ist nur ein Objekt und die aufsteigenden Indizes sind nur eine Konvention.

let a = [];
for (let i = 0; i > -10; i -= 1) {
  a[i] = Math.random() * 10;
}

Sie müssen also letztendlich immer noch die gleiche Prüfung durchführen

function withItem<T>(items: T[], index: number, f: (x: T) => void) {
  if (items[index]) {
    f(items[index]);
  }
}

Es wäre sehr nützlich, um Typen wie Sekunde, Minute, Stunde, Tag, Monat usw. zu definieren.

@Frikki Diese Einheiten sind in einem ausreichend begrenzten Intervall angeordnet, sodass es praktisch und unerschwinglich schwierig ist, sie von Hand zu schreiben.

type Hour =
   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
   | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23;

@aluanhaddad Aber kein unsigned int:

type UInt = 0..4294967295;

meh, wie wäre es mit einem Typ wie diesem:

type Factorial<N extends number> = N > 2 ? Factorial<N - 1> * N : N;
type F1 = Factorial<1>; // 1
type F2 = Factorial<2>; // 1 | 2
type F3 = Factorial<3>; // 1 | 2 | 6
type FN = Factorial<number>; // 1 | 2 | 6 | ... 

Verwenden des Operators * aus dem Kommentar von @aleksey-bykov:

type char = 0..255;
type word = char ** 2;
type int = word ** 2;
type bigint = int ** 2;

@streamich Die Verdoppelung der

@streamich , einige Kommentare:

  • Die Verwendung der Begriffe char , word usw. könnte verwirrend sein, da Leute aus anderen Sprachen den Unterschied zwischen statischer Definition und Laufzeitverhalten möglicherweise nicht erkennen.
  • Ihre vorgeschlagene Syntax berücksichtigt nicht die untere Grenze – was ist, wenn sie ungleich Null ist?
  • Ich wäre vorsichtig, den Exponentiationsoperator für die Verwendung in einem Umgebungs-/Typkontext zu verwenden, da er bereits zu ES2016 hinzugefügt wurde.

Lassen Sie uns einfach das Typsystem vervollständigen und das Halt-Problem genießen Ctrl + Shift + B treffen

@aleksey-bykov sicher erinnerst du dich an dieses schöne Thema 😀

@aluanhaddad

diese Einheiten [Zeiteinheiten] sind in einem ausreichend begrenzten Intervall, so dass es praktisch und unerschwinglich schwierig ist, sie von Hand zu schreiben.

https://github.com/Microsoft/TypeScript/issues/15480#issuecomment -349270853

Jetzt mach das mit Millisekunden :wink:

Ist dieses Thema tot?

@Palid es ist mit [Needs Proposal] markiert, also bezweifle ich das.

So viel Spaß die Diskussion auch gemacht hat, die meisten von uns haben es versäumt, überzeugende Anwendungsfälle aus der realen Welt bereitzustellen.

Siehe https://github.com/Microsoft/TypeScript/issues/15480#issuecomment -324152700

__ANWENDUNGSFÄLLE:__

  1. Sie können genaue int-Typen definieren, sodass eine Teilmenge von TypeScript in WebAssembly oder ein anderes Ziel kompiliert werden kann.

  2. Ein weiterer Anwendungsfall ist UInt8Array , Int8Array , Uint16Array usw., wenn Sie Daten von diesen lesen oder schreiben, könnte TypeScript auf Fehler prüfen.

const ab = new ArrayBuffer(1e3);
const uint8 = new UInt8Array(ab);

uint8[0] = 0xFFFFFFFF; // TSError: Number too big!
  1. Ich habe einige Anwendungsfälle in meinem OP erwähnt.

  2. Wenn Sie dies implementieren, wird die TypeScript-Community Millionen von Anwendungsfällen finden.

Ich habe einen wirklich lustigen Anwendungsfall, der eigentlich allem anderen hier vorgeschlagenen näher kommt.
API nimmt eine ganze Zahl zwischen einem Bereich (in meinem Fall 5-30) und wir müssen dafür ein SDK implementieren.
Manuell darunter zu schreiben ist mühsam (obwohl es irgendwie automatisiert werden kann) für 25 Werte, aber was ist mit Hunderten oder Tausenden?
type X = 5 | 6 | 7 | 8 | 9 | 10 ... | 30

@Palid Das ist der beste und einfachste Fall, den ich für diese Funktion gesehen habe.

Wenn ein Bereich wie 5..30 als syntaktischer Zucker für 5 | 6 | 7 ... | 30 (oder identisch damit funktioniert), würde ich wetten, dass dies ein einfacher Gewinn wäre. Hier würde es für einen diskreten Bereich von ganzen Zahlen stehen.

Vielleicht könnte ein kontinuierlicher Bereich (im Gegensatz zu einem diskreten) mit einer Zahl mit einem Punkt notiert werden - 5.0..30.0 .

Ich habe tatsächlich überlegt, mir Typescript anzusehen, um einen Tippschutz für zu implementieren
https://www.npmjs.com/package/memory-efficient-object

Die Bereichstypisierung würde es einfacher machen, einen möglichen Speicherüberlauf bei der Typprüfung zu erkennen

Scheint so, als ob wir keine Unionserweiterung wirklich brauchen, sondern eher eine inklusive/exklusive Bereichsdefinition, die nur den Bereich vergleicht

type TTerminalColors = int & [0,15];
// int && v >= 0 && v <= 15

Dies wäre sehr nützlich bei der Steuerung von Motoren (z. B. mit Johnny-Five), bei denen die Geschwindigkeit im Bereich von 0-255 liegt.

Ein weiterer Anwendungsfall: Ich implementiere ein Canvas-basiertes Zeichenprogramm mit TypeScript und möchte Typprüfungen auf die Deckkraft (die eine Zahl zwischen 0.0 und 1.0 sein muss) haben.

Nur nachdenken... um das richtig umzusetzen, müsste man wirklich alles geben:

  • Unterstützung von Laufzeittyp-Wächterfunktionen
  • Typeinschränkung für Bedingungen wie x <= 10
  • Unterstützung für string | number Bereiche (da x == 5 true für x === "5" ) zusätzlich zu reinen Zahlenbereichen
  • wahrscheinlich sogar Unterstützung für einen neuen int Typ (der ein Untertyp von number ) und Unterstützung für int only und string | int Bereiche. Geben Sie auch die Eingrenzung für Ausdrücke wie x|0

Tolle Idee, aber das wäre viel zu erzählen!

Vielleicht brauchen wir keinen Laufzeittypschutz. Stattdessen könnten wir die Kompilierzeit ganz nach oben schützen

Dies würde stattdessen eine ausgefeilte Verzweigungsvorhersage erfordern

Vermuten

type TTerminalColors = int & [0,15];

function A(color: TTerminalColors):void
{

}

A(15); // OK
var x = 15;
A(x); // OK

function B(value: int) : void
{
    A(value); // ERROR!!!
    if(value >= 0 && value <= 15)
        A(value); // OK, because we check that it is in the range of TTerminalColors
}

function C(value: int) : void
{
    if(value < 0)
        value = 0;
    if(value > 15)
        value = 15;

    A(value); // OK, because is clamped. But maybe too hard to implemented
}

function ClampInt(value: int): TTerminalColors
{
    if(value >= 0 && value <= 15)
        return value; // Same as B(int)

    if(value > 15)
        return 15;

    return 0;
}

Mein Anwendungsfall:

export const ajax: (config: AjaxOptions) => void;

type Callback = Success | Error;
type Success = (data: any, statusText: string, xhr: XMLHttpRequest) => void;
type Error = (xhr: XMLHttpRequest, statusText: string) => void;

interface AjaxOptions {
  // ...
  statusCode: { [code: number]: Callback | Callback[] },
  // ...
}

Es wäre schön, die Schlüssel in der Option statusCode so einschränken zu können, dass zur Kompilierzeit festgestellt werden kann, ob ein Statuscode einem Erfolgs- oder Fehlercode entspricht:

interface AjaxOptions {
  // ...
  statusCode: {
    200..300: Success,
    400..600: Error
  },
  // ...
}

IMO sollte dies auf Floats und Integer beschränkt und nicht als Union implementiert werden, sondern als neuer Bereichstyp. Dann wäre die Typprüfung so einfach wie:

if (val >= range.start && val < range.end) {
  return match;
} else {
  return no_match;
}

Wir könnten vielleicht ein Blatt aus Rubys Buch nehmen und .. für inklusive Bereiche ( [start, stop] ) und ... für nicht inklusive Bereiche ( [start, stop) ) verwenden.

Ein anderer Anwendungsfall wäre, Ihre Datenbankeinträge zu überprüfen:

Ein weiterer Anwendungsfall wäre die Typüberprüfung von Lat/Lon-Werten.

Dies könnte durch den allgemeinen Validierungsmechanismus von #8665 abgedeckt werden (nicht sicher, warum es als Duplikat geschlossen wurde):

type TTerminalColors (n: number) => Math.floor(n) == n && n >= 0 && n <= 15;
type TRgbColorComponent (n: number) => Math.floor(n) == n && n >= 0 && n <= 255;
type TUInt (n: number) => n >= 0 && n <= 0..4294967295;

Oder mit #4639 genommen und angenommen, dass der Integer-Typ ohne Vorzeichen als uint :

type TTerminalColors (n: uint) => n <= 15;
type TRgbColorComponent (n: uint) => n <= 255;

Mein Anwendungsfall sind globale Koordinaten. Ich möchte Breiten- und Längengrad eingeben, damit sie nur in die spezifischen Bereiche fallen (-90 bis 90 und -180 bis 180).

Edit: Lat und Long haben einen negativen Bereich

Mein Anwendungsfall ist die Implementierung von Arrays mit fester Größe, wobei size ein Parameter ist.

Zum Beispiel möchte ich einen Typ für Arrays von Strings der Länge 2 definieren.

let d: FixedSizeArray<2, string>;
d = [ 'a', 'b' ]; // ok
d = [ 'a' ]; // type error
d = [ 'a', 'b', 'c' ]; // type error
d[0] = 'a1'; // ok
d[1] = 'b1'; // ok
d[2] = 'c1' // type error

Mit der aktuellen Version von TS ist es möglich, etwas zu definieren, das der obigen "Spezifikation" d[1] = 'b1' gibt einen Typfehler zurück, selbst wenn er korrekt ist. Um den Fehler zu vermeiden, muss die Liste aller Rechtsindizes von Hand in der Definition von FixedSizeArray , was langweilig ist.

Wenn wir einen range Operator ähnlich dem keyof Operator hätten, sollte die folgende Typdefinition das Problem lösen.

type FixedSizeArray<U extends number, T> = {
    [k in range(U)]: T;
} & { length: U };

Wobei range(N) eine Abkürzung für range(0,N) .

Gegeben numerische Literale (Naturwerte) M, N mit M < N,

type r = range(M, N); 

ist äquivalent zu

type r = M | M+1 | ... | N-1

Was allgemeiner sein könnte, wäre, wenn wir die Möglichkeit hätten, Prädikats-Lambdas zu definieren und sie als Typen zu verwenden. Beispiel:

predicate mypredicate = (x) => x > 1 && x < 10 
let x: mypredicate = 11 // not ok
let x: mypredicate = 5 // ok

Ein Profi wäre eine geringe Syntaxkomplexität, da Lambdas bereits verfügbar sind, alles, was wir brauchen, ist die Möglichkeit, sie als Typen zu verwenden, die Typprüfung ist sowieso typskriptspezifisch (beachten Sie die Philosophie des "Supersatzes zu Javascript").
Ein Nachteil besteht darin, dass die Komplexität des Prädikats die Leistung der Werkzeuge bestimmt, um Feedback zu geben.

Sicherzustellen, dass eine Zahl/ein Zeichen zu einer arithmetischen Progression gehört, könnte ein guter häufiger Anwendungsfall sein:

// a is the starting element d is the difference between two elements and L is the last element
const belongsToAP = (a, d, L) => {
  return (x) => {
    if(x < a || x > L) return false
    let n = ((x-a)/d) + 1
    if(Number.isInteger(n)) return true
    return false
  }
}

Dies würde es uns ermöglichen, Typprüfungen durchzuführen wie:
Prädikat gehörtzuMeinAP = gehörtzuAP(1,1, 10)

let x : belongsToMyAP = 5 // ok
let y : belongsToMyAP = 7.2 // not ok

Dies kann auch auf Charaktere erweitert werden.

@Kasahs Etwas Ähnliches wurde bereits in #8665 vorgeschlagen.

Ich werfe mein Los mit dem Anwendungsfall "Reflektieren einer API". Ein REST-Endpunkt, für den ich eine Wrapper-Funktion schreibe, nimmt eine ganze Zahl im Bereich 1..1000 als Argument. Es erzeugt einen Fehler, wenn die Zahl diese Einschränkung nicht erfüllt.

Also schreibe ich einen Vorschlag für numerische Bereiche und bin auf dieses Problem gestoßen, bei dem ich nicht sicher bin, wie ich damit umgehen soll, also werfe ich es zur Prüfung raus.

Da der TypeScript-Compiler in TypeScript geschrieben ist, ist es möglich, die Eigenschaften der numerischen Operatoren zu nutzen, um Bereichstypen zu ändern.

// Syntax: x..y for an inclusive integer range.

let x: 0..10 = randomNumber(0, 10);
let y = x + 2; // Can deduce that y: 2..12.

Dies ist in Ordnung, wenn Sie einer neuen Variablen zuweisen, aber was ist mit der Mutation?

let x: 0..10 = randomNumber(0, 10);
x += 2; // Error: 2..12 is not assignable to type 0..10 (upper bound is out of range).

Dieser Fehler wäre technisch korrekt, aber in der Praxis wäre es unglaublich ärgerlich, damit umzugehen. Wenn wir eine Variable, die von einer Funktion mit einem Range-Rückgabetyp zurückgegeben wird, mutieren möchten, müssten wir immer eine Typ-Assertion hinzufügen.

let x = randomNumber(0, 10) as number; // If randomNumber doesn't return a type assigable to number
// this will be an error, but it would still be annoying to have to sprinkle "as number"
// expressions everywhere.

Und wenn wir es ignorieren, erhalten wir die folgende Unzulässigkeit:

function logNumber0To10 (n: 0..10): void {
    console.log(n);
}

let x: 0..10 = randomNumber(0, 10);
x += 2; // Because we're ignoring mutations, x: 0..10, but the runtime value could be 11 or 12,
// which are outside the specified range...
logNumber0To10(x); // ...which means we lose type safety on this call.

Eine Lösung hierfür wäre die Möglichkeit, den Typ einer Variablen nach der Deklaration zu ändern, also würde Beispiel 2 nur den Typ von x in 2..12 ändern; aber mein erster Instinkt ist, dass dies zu viel Overhead in den Compiler einbringen und die Benutzer verwirren würde.

Und was ist mit benutzerdefinierten Funktionen und Generika?

// How to define this return type, seeing as we can't do math in types?
function increment<L extends number, H extends number> (x: L..H): (L + 1)..(H + 1);

Irgendwelche Gedanken, wie man mit dem oben genannten umgeht?

@JakeTunaley

was ist mit Mutationen?

Warum sollte Mutation anders sein als jede andere Zuordnung?

Hier ist mein bescheidener Versuch eines Vorschlags. Dieser Vorschlag verlangt auch, dass andere Typen hinzugefügt werden.

  • Infinity Typ

    • Hat nur den Wert Infinity

  • -Infinity Typ

    • Hat nur den Wert -Infinity

  • NaN Typ

    • Hat nur den Wert NaN

  • double Typ

    • Alle number Werte im Bereich [-Number.MAX_VALUE, Number.MAX_VALUE] oder [-1.7976931348623157e+308, 1.7976931348623157e+308]

  • number ist nur Infinity|-Infinity|NaN|double
  • int Typ

    • Ein Untertyp von double

    • Alle number Werte x im Bereich [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER] oder [-9007199254740991, 9007199254740991] und Math.floor(x) === x

    • 3 und 3.0 wären also Werte vom Typ int

  • Typ "Endliches Literal"

    • Beispiele sind 1 , 3.141 , 45

    • Kann Untertypen von double oder int

  • Der Typ "GtEq" wird mit (>= x)

    • Dabei ist x ein endliches Literal, Infinity oder -Infinity

  • Der Typ "LtEq" wird mit (<= x)

    • Dabei ist x ein endliches Literal, Infinity oder -Infinity

  • Der Typ "Gt" wird mit (> x)

    • Dabei ist x ein endliches Literal, Infinity oder -Infinity

  • Der Typ "Lt" wird mit (< x)

    • Dabei ist x ein endliches Literal, Infinity oder -Infinity


GtEq-Typ; (>= x)

  • (>= Infinity) = Infinity
  • (>= -Infinity) = -Infinity|double|Infinity
  • Infinity ist ein Untertyp von (>= [finite-literal])
  • (>= [finite-literal]) ist ein Untertyp von double|Infinity
  • (>= NaN) = never
  • (>= int) = (>= -9007199254740991)
  • (>= double) = (>= -1.7976931348623157e+308)
  • (>= number) = number

Gt-Typ; (> x)

  • (> Infinity) = never
  • (> -Infinity) = double|Infinity
  • Infinity ist ein Untertyp von (> [finite-literal])
  • (> [finite-literal]) ist ein Untertyp von double|Infinity
  • (> NaN) = never
  • (> int) = (> -9007199254740991)
  • (> double) = (> -1.7976931348623157e+308)
  • (> number) = number

LtEq-Typ; (<= x)

  • (<= Infinity) = -Infinity|double|Infinity
  • (<= -Infinity) = -Infinity
  • -Infinity ist ein Untertyp von (<= [finite-literal])
  • (<= [finite-literal]) ist ein Untertyp von -Infinity|double
  • (<= NaN) = never
  • (<= int) = (<= 9007199254740991)
  • (<= double) = (<= 1.7976931348623157e+308)
  • (<= number) = number

Lt-Typ; (< x)

  • (< Infinity) = -Infinity|double
  • (< -Infinity) = never
  • -Infinity ist ein Untertyp von (< [finite-literal])
  • (< [finite-literal]) ist ein Untertyp von -Infinity|double
  • (< NaN) = never
  • (< int) = (< 9007199254740991)
  • (< double) = (< 1.7976931348623157e+308)
  • (< number) = number

Bereichstypen

Beachten Sie, dass wir zwar Dinge wie (>= Infinity) , (> number) usw. schreiben können,
der resultierende Typ ist kein Bereichstyp; Sie sind nur Aliase für andere Typen.

Ein Bereichstyp ist einer von

  • (>= [finite-literal])
  • (> [finite-literal])
  • (<= [finite-literal])
  • (< [finite-literal])

Wir erlauben Syntax wie (> number) und ähnliche für die Verwendung in Generika.


Union GtEq/Gt-Typen

Bei der Vereinigung zweier GtEq/Gt-Typen ergibt sich der Typ mit "mehr" Werten,

  • (>= [finite-literal-A]) | (>= [finite-literal-B]) = ...

    • Wenn [finite-literal-A] >= [finite-literal-B] , dann ist das Ergebnis (>= [finite-literal-B])

    • Andernfalls lautet das Ergebnis (>= [finite-literal-A])

    • zB (>= 3) | (>= 5.5) = (>= 3) weil (>= 3) ein Supertyp von (>= 5.5)

  • (>= [finite-literal-A]) | (> [finite-literal-B]) = ...

    • Wenn [finite-literal-A] == [finite-literal-B] , dann ist das Ergebnis (>= [finite-literal-A])

    • Wenn [finite-literal-A] > [finite-literal-B] , dann ist das Ergebnis (> [finite-literal-B])

    • Andernfalls lautet das Ergebnis (>= [finite-literal-A])

  • (> [finite-literal-A]) | (> [finite-literal-B]) = ...

    • Wenn [finite-literal-A] >= [finite-literal-B] , dann ist das Ergebnis (> [finite-literal-B])

    • Andernfalls lautet das Ergebnis (> [finite-literal-A])

    • zB (> 3) | (> 5.5) = (> 3) weil (> 3) ein Supertyp von (> 5.5)

Ebenfalls,

  • (>= A|B) = (>= A) | (>= B)
  • (> A|B) = (> A) | (> B)

    • zB (> 4|3) = (> 4) | (> 3) = (> 3)

    • zB (> number|3) = (> number) | (> 3) = number | (> 3) = number

Union LtEq/Lt-Typen

Bei der Vereinigung zweier LtEq/Lt-Typen ergibt sich der Typ mit "mehr" Werten,

  • (<= [finite-literal-A]) | (<= [finite-literal-B]) = ...

    • Wenn [finite-literal-A] <= [finite-literal-B] , dann ist das Ergebnis (<= [finite-literal-B])

    • Andernfalls lautet das Ergebnis (<= [finite-literal-A])

    • zB (<= 3) | (<= 5.5) = (<= 5.5) weil (<= 5.5) ein Supertyp von (<= 3)

  • (<= [finite-literal-A]) | (< [finite-literal-B]) = ...

    • Wenn [finite-literal-A] == [finite-literal-B] , dann ist das Ergebnis (<= [finite-literal-A])

    • Wenn [finite-literal-A] < [finite-literal-B] , dann ist das Ergebnis (< [finite-literal-B])

    • Andernfalls lautet das Ergebnis (<= [finite-literal-A])

  • (< [finite-literal-A]) | (< [finite-literal-B]) = ...

    • Wenn [finite-literal-A] <= [finite-literal-B] , dann ist das Ergebnis (< [finite-literal-B])

    • Andernfalls lautet das Ergebnis (< [finite-literal-A])

    • zB (< 3) | (< 5.5) = (< 5.5) weil (< 5.5) ein Supertyp von (< 3)

Ebenfalls,

  • (<= A|B) = (<= A) | (<= B)
  • (< A|B) = (< A) | (< B)

    • zB (< 4|3) = (< 4) | (< 3) = (< 4)

    • zB (< number|3) = (< number) | (< 3) = number | (< 3) = number


Schnittpunkt GtEq/Gt-Typen

Beim Schnittpunkt zweier GtEq/Gt-Typen ergibt sich der Typ mit "weniger" Werten,

  • (>= [finite-literal-A]) & (>= [finite-literal-B]) = ...

    • Wenn [finite-literal-A] >= [finite-literal-B] , dann ist das Ergebnis (>= [finite-literal-A])

    • Andernfalls lautet das Ergebnis (>= [finite-literal-B])

    • zB (>= 3) & (>= 5.5) = (>= 5.5) weil (>= 5.5) ein Untertyp von (>= 3)

  • (>= [finite-literal-A]) & (> [finite-literal-B]) = ...

    • Wenn [finite-literal-A] == [finite-literal-B] , dann ist das Ergebnis (> [finite-literal-B])

    • Wenn [finite-literal-A] > [finite-literal-B] , dann ist das Ergebnis (>= [finite-literal-A])

    • Andernfalls lautet das Ergebnis (> [finite-literal-B])

  • (> [finite-literal-A]) & (> [finite-literal-B]) = ...

    • Wenn [finite-literal-A] >= [finite-literal-B] , dann ist das Ergebnis (> [finite-literal-A])

    • Andernfalls lautet das Ergebnis (> [finite-literal-B])

    • zB (> 3) & (> 5.5) = (> 5.5) weil (> 5.5) ein Untertyp von (> 3)

Schnittpunkt LtEq/Lt-Typen

Beim Schnittpunkt zweier LtEq/Lt-Typen ergibt sich der Typ mit "weniger" Werten,

  • (<= [finite-literal-A]) & (<= [finite-literal-B]) = ...

    • Wenn [finite-literal-A] <= [finite-literal-B] , dann ist das Ergebnis (<= [finite-literal-A])

    • Andernfalls lautet das Ergebnis (<= [finite-literal-B])

    • zB (<= 3) & (<= 5.5) = (<= 3) weil (<= 3) ein Untertyp von (<= 5.5)

  • (<= [finite-literal-A]) & (< [finite-literal-B]) = ...

    • Wenn [finite-literal-A] == [finite-literal-B] , dann ist das Ergebnis (< [finite-literal-B])

    • Wenn [finite-literal-A] < [finite-literal-B] , dann ist das Ergebnis (<= [finite-literal-A])

    • Andernfalls lautet das Ergebnis (< [finite-literal-B])

  • (< [finite-literal-A]) & (< [finite-literal-B]) = ...

    • Wenn [finite-literal-A] <= [finite-literal-B] , dann ist das Ergebnis (< [finite-literal-A])

    • Andernfalls lautet das Ergebnis (< [finite-literal-B])

    • zB (< 3) & (< 5.5) = (< 3) weil (< 3) ein Untertyp von (< 5.5)


Anwendungsfälle

  • Um statisch sicherzustellen, dass eine ganze Zahl in einen MySQL-Datentyp UNSIGNED INT passt,

    //TODO Propose numeric and range sum/subtraction/multiplication/division/mod/exponentiation types?
    function insertToDb (x : int & (>= 0) & (<= 4294967295)) {
      //Insert to database
    }
    
  • Um statisch sicherzustellen, dass eine Zeichenfolge eine bestimmte Länge hat,

    function foo (s : string & { length : int & (>= 1) & (<= 255) }) {
      //Do something with this non-empty string that has up to 255 characters
    }
    
  • Um statisch sicherzustellen, dass ein Array-ähnliches Objekt den entsprechenden length Wert hat,

    function foo (arr : { length : int & (>= 0) }) {
      //Do something with the array-like object
    }
    
  • Um statisch sicherzustellen, dass wir nur endliche Zahlen erhalten,

    function foo (x : double) {
      //`x` is NOT NaN|Infinity|-Infinity
    }
    
  • Um statisch sicherzustellen, dass Array-Indizes vorhanden sind?

    function foo (arr : { [index : int & (>= 0) & (< 10)] : string }) {
      console.log(arr[0]); //OK
      console.log(arr[1]); //OK
      console.log(arr[2]); //OK
      console.log(arr[9]); //OK
      console.log(arr[10]); //Error
    }
    

Ich würde gerne numerische und Bereichsadditions- / Subtraktions- / Multiplikations- / Divisions- / Mod- / Exponentiationstypen vorschlagen, aber das scheint bei diesem Problem nicht in den Anwendungsbereich zu fallen.

[Bearbeiten]
Sie könnten double umbenennen und float aber ich dachte nur, dass double genauer darstellt, dass dies eine Gleitkommazahl mit doppelter Genauigkeit ist.

[Bearbeiten]

Einige Typen wurden in never geändert.

Wäre es möglich, dass der Compiler die Flussanalyse durchführt?

Angenommen, es gibt diese Funktion

function DoSomething(x : int & (>= 0) & (< 10)){
   // DoSomething
}

function WillError(x : int){
    DoSomething(x); // error; x is not >= 0 & < 10
}

function WillNotError(x : int){
    if(x >= 0 && x < 10)
        DoSomething(x); // not error by flow analysis
}

Ein weiterer Anwendungsfall: Ich habe eine Zahleneingabe in eine Funktion, die einen Prozentsatz darstellt. Ich möchte die Werte auf 0 und 1 begrenzen.

Ich habe gerade [...Array(256)].map((_,i) => i).join("|") , um meine bisher hässlichste Typdefinition zu erstellen

Nichtnegative ganze Zahlen und kleine Zahlen sind möglich:

type ArrayT<T> = T extends (infer P)[] ? P : never;
type A = ArrayT<Range<5, 10>>;//5|6|7|8|9|10

Bereich : https://github.com/kgtkr/typepark/blob/master/src/list.ts

Mit "kleinen Zahlen" meinen Sie, ohne Stufe 3 BigInt ?

@Mouvedia Eine Zahl, die in die Rekursionsgrenze des Compilers passt

Vielleicht verwenden Sie .. für Integer und ... für Floats.

Ich würde sagen, dass .. inklusiver Bereich und ... exklusiv bedeuten sollte. Genau wie in zB Ruby. Siehe http://rubylearning.com/satishtalim/ruby_ranges.html

Ich bin kein Fan davon, einen einzigen Zeitraum zu verwenden, um zwischen Inklusiv- und Exklusivbereichen zu unterscheiden

Kann ich einspringen? Ich denke, dass diese Funktion, die als einzelne Verbesserung für die Typspezifikation angesehen wird, wahrscheinlich nicht der beste Weg ist, hier vorzugehen.

type range = 1:2:Infinity // 1, 3, 5… Infinity

Auf diese Weise definieren Sie in vielen 4gl-Plattformen einen numerischen Bereich – insbesondere Matrix-orientierten.

Sie machen es so, denn ein einheitlicher Bereich ist konventionell einer der besten Modellierungsansätze.

Ein sequentieller Bereich ist also dieser:

type decmials = 1:10 // like 1 .. 10 with all decimals in between

Und wenn nur die ganzen Zahlen:

type integers = 1:1:10 // 1, 2, 3, 4, 5, 6, 7, 8, 10

// OR

type integers = number.integers<1:10>

Wenn wir mit der Syntax herumspielen wollen:

type something = 1::10 // whatever use cases need today

Wenn das nicht Tokenizer-freundlich ist, ist dies meiner Meinung nach nicht die richtige Blockierungspriorität, auf die man sich hier konzentrieren sollte. Ich weise darauf hin, in der Hoffnung, nicht zu sehen, dass eine Lösung uns beim Erreichen der nächsten einschränkt.

Bearbeiten : Was ich nicht berücksichtige, ist der Aspekt der Inklusivität – und hier müssen wir uns fragen, ob wir genug Sorgfalt walten lassen, um zu verstehen, warum dies kein Problem war, als die Leute so viele Probleme lösten, die sich auf einheitliche Bereiche stützten, die implizit jede Zahl einschließen außer wenn das Inkrement nicht genau mit dem Ende des Bereichs übereinstimmt.

Interessant, dass Sie in der Lage sind, "Schrittgrößen" in einem Bereichstyp zu definieren.

Abgesehen von den Bereichen "Gleitkomma" (keine feste Schrittgröße) und "Ganzzahl" (Schrittgröße von 1, beginnt mit einer Ganzzahl) bin ich noch nie auf einen realen Anwendungsfall für Bereiche mit anderen Schrittgrößen gestoßen.

Es ist also interessant zu hören, dass es anderswo anders ist. Jetzt muss ich etwas über 4gl lernen, weil ich noch nie davon gehört habe.


Die Möglichkeit, ein halboffenes Intervall definieren zu können, ist für Array-ähnliche Objekte nützlich. Etwas wie,

interface SaferArray<T, LengthT extends integer & (>= 0)> {
    length : LengthT;
    [index in integer & (>= 0) & (< LengthT)] : T
}

Wenn wir nur Inklusivbereiche hätten, bräuchten wir (<= LengthT - 1) aber das ist einfach weniger elegant

Kann ich einspringen? Ich denke, dass diese Funktion, die als einzelne Verbesserung für die Typspezifikation angesehen wird, wahrscheinlich nicht der beste Weg ist, hier vorzugehen.

type range = 1:2:Infinity // 1, 3, 5… Infinity

Auf diese Weise definieren Sie in vielen 4gl-Plattformen einen numerischen Bereich – insbesondere Matrix-orientierten.

Sie machen es so, denn ein einheitlicher Bereich ist konventionell einer der besten Modellierungsansätze.

Ein sequentieller Bereich ist also dieser:

type decmials = 1:10 // like 1 .. 10 with all decimals in between

Und wenn nur die ganzen Zahlen:

type integers = 1:1:10 // 1, 2, 3, 4, 5, 6, 7, 8, 10

// OR

type integers = number.integers<1:10>

Wenn wir mit der Syntax herumspielen wollen:

type something = 1::10 // whatever use cases need today

Wenn das nicht Tokenizer-freundlich ist, ist dies meiner Meinung nach nicht die richtige Blockierungspriorität, auf die man sich hier konzentrieren sollte. Ich weise darauf hin, in der Hoffnung, nicht zu sehen, dass eine Lösung uns beim Erreichen der nächsten einschränkt.

Bearbeiten : Was ich nicht berücksichtige, ist der Aspekt der Inklusivität – und hier müssen wir uns fragen, ob wir genug Sorgfalt walten lassen, um zu verstehen, warum dies kein Problem war, als die Leute so viele Probleme lösten, die sich auf einheitliche Bereiche stützten, die implizit jede Zahl einschließen außer wenn das Inkrement nicht genau mit dem Ende des Bereichs übereinstimmt.

Hmm sieht aus wie in Haskell. Ich denke, das ist gut. Generatoren ermöglichen auch eine faule Auswertung.

Es ist nicht so, dass Sie keine faule Auswertung mit einer anderen Syntax haben können =x

Interessant, dass Sie in der Lage sind, "Schrittgrößen" in einem Bereichstyp zu definieren.

Ich stelle mir einen Bereich als Start, Ende und Inkrement vor. Wenn das Inkrement also genau auf das Ende rundet, ist es inklusive. Array-Indizes und Längen als Bereichsfunktionen können ein Array sein 0:1:9 hat 10 Indexschritte (Länge). Der Bereich kann hier also bequem integer & 0:9 für ein Typsystem sein, das leichter aus der Kombination dieser beiden Ausdrücke schließen kann.

4GL ist wirklich ein generisches Label, für mich war das hauptsächlich MatLab

Mein Punkt war, dass nur einschließende Bereiche die Verwendung in Generika erschweren würden (es sei denn, Sie implementieren die mathematischen Operationen auf Typebene hack-y).

Denn statt nur "length" als Typparameter zu haben, braucht man jetzt length und max index. Und max index muss gleich length-1 sein.

Und auch hier können Sie nicht überprüfen, ob dies der Fall ist, es sei denn, Sie implementieren diese mathematischen Operationen auf Hack-y-Ebene

@AnyhowStep Ich einordnen können – aber vielleicht hilft es zunächst, dies zu klären:

Wenn wir das Inklusiv-oder-nicht als spezifisch anwendbar auf ein n-1-Problem (wie dieses Index-/Zähl-Szenario) beiseite legen, vorausgesetzt, dass es irgendwie unabhängig gelöst wurde (nur den Gedanken lustig machen), gibt es andere Szenarien, für die numerische Bereiche geeignet wären benötigen Sie immer noch weniger deklarative und/oder unkonventionelle Syntax, um richtig zu beschreiben?

Ich komme darauf, dass es sich um 2 separate Aspekte handelt, Sie benötigen numerische Bereiche, die konventionell an den Erwartungen dieser Domäne ausgerichtet sind, und Sie benötigen auch Prädikate für Index-/Zähltypen ... usw.

Nur positive Zahlen.

declare let x : (> 0);
x = 0.1; //OK
x = 0.0001; //OK
x = 0.00000001; //OK
x = 0; //Error
x = 1; //OK
x = -1; //Error

Bei reinen Inklusivbereichen,

declare let x : epsilon:epsilon:Infinity; //Where epsilon is some super-small non-zero, positive number

Positive Zahlen, außer Infinity ,

declare let x : (> 0) & (< Infinity);

Bei reinen Inklusivbereichen,

const MAX_FLOAT : 1.7976931348623157e+308 = 1.7976931348623157e+308;
declare let x : epsilon:epsilon:MAX_FLOAT;

Auch unabhängig von der aktuellen Diskussion, aber hier ist ein weiterer Grund, warum ich wirklich numerische Bereichstypen möchte.

Ein Großteil meiner täglichen Arbeit besteht darin, eine Reihe verschiedener Softwaresysteme zusammenzuschustern. Und viele dieser Systeme haben unterschiedliche Anforderungen an Daten mit gleicher Bedeutung.

zB (systemA, Wert zwischen 1 und 255), (systemB, Wert zwischen 3 und 73) usw.
zB (systemC. Stringlänge 7-88), (systemD, Stringlänge 9-99), (systemE, Stringlänge 2-101) usw.

Im Moment muss ich all diese separaten Anforderungen sorgfältig dokumentieren und sicherstellen, dass Daten von einem System korrekt auf ein anderes System abgebildet werden können. Wenn es nicht zuordnet, muss ich Workarounds finden.

Ich bin nur ein Mensch. Ich mache Fehler. Ich weiß nicht, dass Daten manchmal nicht zugeordnet werden können. Reichweitenprüfungen schlagen fehl.

Mit numerischen Bereichstypen kann ich endlich die Bereiche deklarieren, die jedes System erwartet, und den Compiler die Prüfung für mich durchführen lassen.


Zum Beispiel hatte ich gerade eine Situation, in der die von mir verwendete API eine Beschränkung der Zeichenfolgenlänge von 10k für alle Zeichenfolgenwerte hatte. Nun, ich hatte keine Möglichkeit, TypeScript anzuweisen, zu überprüfen, ob alle Zeichenfolgen, die an die API gesendet werden, <= 10k Zeichenfolgenlänge haben.

Ich hatte einen Laufzeitfehler anstelle eines netten Kompilierungsfehlers, bei dem TS gehen könnte,

`string` is not assignable to `string & { length : (<= 10000) }`

@AnyhowStep Ich hoffe, Sie können

Ich bin ehrlich der Meinung, dass Anwendungsfälle immer Funktionen vorantreiben sollten, und denke, dass die Probleme in Bezug auf Index und Länge diejenigen sind, die viele von uns manchmal wirklich vermissen. Also möchte ich dieses Problem einfach unter dem richtigen Etikett ansprechen – ist es ein Zahlentyp oder ein indizierter Typ? Ich kenne die Antwort nicht genau, aber ich zögere, zu glauben, dass die Lösung als Nummerntyp-Anwender nicht versehentlich viel mehr Probleme für Benutzer von Nummerntypen aufwerfen wird, die nicht das gleiche Verständnis dieser Aspekte haben.

Wo sonst würde es für alle Sinn machen, ein solches Problem zu lösen, frage ich mich an dieser Stelle nur, Gedanken?

Ich habe es mit einer API zu tun, die Byte-Arrays übergibt, daher möchte ich einen Byte-Typ definieren:

type byte = 0x00..0xFF
type bytes = byte[]

Dies wäre auch nützlich, wenn Sie mit Uint8Array .

Wenn Sie wie ich und ungeduldig darauf sind, Bereichstypen zu erhalten, die Sie im Moment tatsächlich verwenden können, finden Sie hier einen Codeausschnitt mit Bereichstypen in Aktion.

TL;DR,
Bereichstypen können jetzt funktionieren

interface CompileError<_ErrorMessageT> {
    readonly __compileError : never;
}
///////////////////////////////////////////////
type PopFront<TupleT extends any[]> = (
    ((...tuple : TupleT) => void) extends ((head : any, ...tail : infer TailT) => void) ?
    TailT :
    never
);
type PushFront<TailT extends any[], FrontT> = (
    ((front : FrontT, ...tail : TailT) => void) extends ((...tuple : infer TupleT) => void) ?
    TupleT :
    never
);
type LeftPadImpl<TupleT extends any[], ElementT extends any, LengthT extends number> = {
    0 : TupleT,
    1 : LeftPad<PushFront<TupleT, ElementT>, ElementT, LengthT>
}[
    TupleT["length"] extends LengthT ?
    0 :
    1
];
type LeftPad<TupleT extends any[], ElementT extends any, LengthT extends number> = (
    LeftPadImpl<TupleT, ElementT, LengthT> extends infer X ?
    (
        X extends any[] ?
        X :
        never
    ) :
    never
);
type LongerTuple<A extends any[], B extends any[]> = (
    keyof A extends keyof B ?
    B :
    A
);

///////////////////////////////////////////////////////
type Digit = 0|1|2|3|4|5|6|7|8|9;
/**
 * A non-empty tuple of digits
 */
type NaturalNumber = Digit[];

/**
 * 6 - 1 = 5
 */
type SubOne<D extends Digit> = {
    0 : never,
    1 : 0,
    2 : 1,
    3 : 2,
    4 : 3,
    5 : 4,
    6 : 5,
    7 : 6,
    8 : 7,
    9 : 8,
}[D];

type LtDigit<A extends Digit, B extends Digit> = {
    0 : (
        B extends 0 ?
        false :
        true
    ),
    1 : false,
    2 : LtDigit<SubOne<A>, SubOne<B>>
}[
    A extends 0 ?
    0 :
    B extends 0 ?
    1 :
    2
];


//false
type ltDigit_0 = LtDigit<3, 3>;
//true
type ltDigit_1 = LtDigit<3, 4>;
//false
type ltDigit_2 = LtDigit<5, 2>;


/**
 * + Assumes `A` and `B` have the same length.
 * + Assumes `A` and `B` **ARE NOT** reversed.
 *   So, `A[0]` is actually the **FIRST** digit of the number.
 */
type LtEqNaturalNumberImpl<
    A extends NaturalNumber,
    B extends NaturalNumber
> = {
    0 : true,
    1 : (
        LtDigit<A[0], B[0]> extends true ?
        true :
        A[0] extends B[0] ?
        LtEqNaturalNumberImpl<
            PopFront<A>,
            PopFront<B>
        > :
        false
    ),
    2 : never
}[
    A["length"] extends 0 ?
    0 :
    number extends A["length"] ?
    2 :
    1
];
type LtEqNaturalNumber<
    A extends NaturalNumber,
    B extends NaturalNumber
> = (
    LtEqNaturalNumberImpl<
        LeftPad<A, 0, LongerTuple<A, B>["length"]>,
        LeftPad<B, 0, LongerTuple<A, B>["length"]>
    > extends infer X ?
    (
        X extends boolean ?
        X :
        never
    ) :
    never
);

//false
type ltEqNaturalNumber_0 = LtEqNaturalNumber<
    [1],
    [0]
>;
//true
type ltEqNaturalNumber_1 = LtEqNaturalNumber<
    [5,2,3],
    [4,8,9,2,3]
>;
//false
type ltEqNaturalNumber_2 = LtEqNaturalNumber<
    [4,8,9,2,3],
    [5,2,3]
>;
//true
type ltEqNaturalNumber_3 = LtEqNaturalNumber<
    [5,2,3],
    [5,2,3]
>;
//true
type ltEqNaturalNumber_4 = LtEqNaturalNumber<
    [5,2,2],
    [5,2,3]
>;
//false
type ltEqNaturalNumber_5 = LtEqNaturalNumber<
    [5,1],
    [2,5]
>;
//false
type ltEqNaturalNumber_6 = LtEqNaturalNumber<
    [2,5,7],
    [2,5,6]
>;

type RangeLt<N extends NaturalNumber> = (
    number &
    {
        readonly __rangeLt : N|undefined;
    }
);
type StringLengthLt<N extends NaturalNumber> = (
    string & { length : RangeLt<N> }
);

type AssertStringLengthLt<S extends StringLengthLt<NaturalNumber>, N extends NaturalNumber> = (
    LtEqNaturalNumber<
        Exclude<S["length"]["__rangeLt"], undefined>,
        N
    > extends true ?
    S :
    CompileError<[
        "Expected string of length less than",
        N,
        "received",
        Exclude<S["length"]["__rangeLt"], undefined>
    ]>
);
/**
 * String of length less than 256
 */
type StringLt256 = string & { length : RangeLt<[2,5,6]> };
/**
 * String of length less than 512
 */
type StringLt512 = string & { length : RangeLt<[5,1,2]> };

declare function foo<S extends StringLengthLt<NaturalNumber>> (
    s : AssertStringLengthLt<S, [2,5,6]>
) : void;

declare const str256 : StringLt256;
declare const str512 : StringLt512;

foo(str256); //OK!
foo(str512); //Error

declare function makeLengthRangeLtGuard<N extends NaturalNumber> (...n : N) : (
    (x : string) => x is StringLengthLt<N>
);

if (makeLengthRangeLtGuard(2,5,6)(str512)) {
    foo(str512); //OK!
}

declare const blah : string;
foo(blah); //Error

if (makeLengthRangeLtGuard(2,5,5)(blah)) {
    foo(blah); //OK!
}

if (makeLengthRangeLtGuard(2,5,6)(blah)) {
    foo(blah); //OK!
}

if (makeLengthRangeLtGuard(2,5,7)(blah)) {
    foo(blah); //Error
}

Spielplatz

Es verwendet den Typ CompileError<> von hier aus,
https://github.com/microsoft/TypeScript/issues/23689#issuecomment -512114782

Der Typ AssertStringLengthLt<> ist der Ort, an dem die Magie passiert

Wenn Sie die Addition auf Typebene verwenden, können Sie str512 + str512 und erhalten ein str1024

https://github.com/microsoft/TypeScript/issues/14833#issuecomment -513106939

Wenn ich mir die @AnyhowStep- Lösung temporäre Lösung habe. Erinnern Sie sich an Typenwächter?:

/**
 * Just some interfaces
 */
interface Foo {
    foo: number;
    common: string;
}

interface Bar {
    bar: number;
    common: string;
}

/**
 * User Defined Type Guard!
 */
function isFoo(arg: any): arg is Foo {
    return arg.foo !== undefined;
}

/**
 * Sample usage of the User Defined Type Guard
 */
function doStuff(arg: Foo | Bar) {
    if (isFoo(arg)) {
        console.log(arg.foo); // OK
        console.log(arg.bar); // Error!
    }
    else {
        console.log(arg.foo); // Error!
        console.log(arg.bar); // OK
    }
}

doStuff({ foo: 123, common: '123' });
doStuff({ bar: 123, common: '123' });

Schauen Sie sich also folgenden Code an:

class NumberRange {
    readonly min: number;
    readonly max: number;
    constructor(min:number, max:number, ) {
        if (min > max) { 
            throw new RangeError(`min value (${min}) is greater than max value (${max})`);
        } else {
            this.min = min;
            this.max = max;
        }
    }
    public isInRange = (num: number, explicit = false): boolean => {
        let inRange: boolean = false;
        if (explicit === false) {
            inRange = num <= this.max && num >= this.min;
        } else {
            inRange = num < this.max && num > this.min;
        }
        return inRange;
    };
}
const testRange = new NumberRange(0, 12);
if(testRange.isInRange(13)){
    console.log('yay')
}else {
  console.log('nope')
}

Es ist nur eine Idee, aber mit Typenwächtern ist es möglich, Bereiche zu verwenden.

Das Problem hier ist, dass Sie dies nicht tun können,

declare const a : number;
declare let b : (>= 5);
const testRange = new NumberRange(12, 20);
if (testRange.isInRange(a)) {
  b = a; //ok
} else {
  b = a; //compile error
}

Typenschutz allein ist keine zufriedenstellende Lösung

Außerdem verwendet Ihr Beispiel nicht einmal Typwächter.


In meinem albernen Hacky-Toy-Beispiel oben können Sie einen Bereichstyp einem anderen Bereichstyp zuweisen (durch Funktionsparameter), wenn seine Grenzen innerhalb des anderen liegen.


Außerdem sind Tag-Typen, Nominaltypen und Wertobjekte, die auf Primitiven verwendet werden, normalerweise ein Zeichen dafür, dass das Typsystem nicht ausdrucksstark genug ist.

Ich hasse mein dummes Spielzeugbeispiel, das Tag-Typen verwendet, weil es extrem unergonomisch ist. In diesem langen Kommentar erfahren Sie, warum es für Bereiche besser ist, primitiv zu sein.

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -548249683

Dies können Sie mit der aktuellen Typescript-Version erreichen:

// internal helper types
type IncrementLength<A extends Array<any>> = ((x: any, ...xs: A) => void) extends ((...a: infer X) => void) ? X : never;
type EnumerateRecursive<A extends Array<any>, N extends number> = A['length'] extends infer X ? (X | { 0: never, 1: EnumerateRecursive<IncrementLength<A>, N> }[X extends N ? 0 : 1]) : never;

// actual utility types
export type Enumerate<N extends number> = Exclude<EnumerateRecursive<[], N>, N>;
export type Range<FROM extends number, TO extends number> = Exclude<Enumerate<TO>, Enumerate<FROM>>;

// usage examples:
type E1 = Enumerate<3>; // hover E1: type E1 = 0 | 1 | 2
type E2 = Enumerate<10>;  // hover E2: type E2 = 0 | 1 | 3 | 2 | 4 | 5 | 6 | 7 | 8 | 9

type R1 = Range<0, 5>; // hover R1: type R1 = 0 | 1 | 3 | 2 | 4
type R2 = Range<5, 11>; // hover R2: type R2 = 10 | 5 | 6 | 7 | 8 | 9

Ich habe mich an die Konvention gehalten, inklusive Start- und exklusive Endindizes zu verwenden, aber Sie können sie an Ihre Bedürfnisse anpassen.

Die vs-Code-Hover-Hinweise in Kommentaren.

Beachten Sie, dass die Zahlen in den Hover-Hinweisen zufällig sortiert sind.

image

Leider funktioniert es nur bis zu etwa 10 Elementen :(

Bearbeiten: Es scheint, dass Enumerate nur bis zu 15 Rekursionen verarbeiten kann (0 - 14)

@Shinigami92
Bei diesem Ansatz verarbeitet Enumerate bis zu 40.
Dies ist immer noch eine Einschränkung, aber in der Praxis möglicherweise nicht so hart.

type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;
type EnumerateInternal<A extends Array<unknown>, N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>, N> }[N extends A['length'] ? 0 : 1];
export type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;
export type Range<FROM extends number, TO extends number> = Exclude<Enumerate<TO>, Enumerate<FROM>>;

type E1 = Enumerate<40>;
type E2 = Enumerate<10>;
type R1 = Range<0, 5>;
type R2 = Range<5, 34>;

Und jetzt wurde es irgendwie magisch sortiert ;).

Typische Anwendungsfälle für mich verwenden Bereiche wie [1, 255] , [1, 2048] , [1, 4096] , [20, 80] usw. Das Erstellen großer Union-Typen kann TS ausflippen/verlangsamen . Aber diese Lösungen funktionieren definitiv für "kleinere" Bereiche

@AnyhowStep
Da wir wissen, dass die Anzahl der Rekursionen eine Grenze darstellt, sollten wir einen Weg finden, die Zahlendivision/Multiplikation mit zwei in einer einzelnen nicht-rekursiven Operation innerhalb der Typdefinition durchzuführen, um diese Bereiche zu erreichen.

Die Leistung ist immer noch ein Thema. Aus diesem Grund musste ich schon früher nützliche Union-Typen aus großen Apps herausfiltern - auch wenn es eine interessante Übung sein könnte, ist es definitiv keine Lösung.

Es gibt immer noch keine perfekten Lösungen, denke ich?

Ich hoffe, dies kann wie folgt im Allgemeinen erfolgen (aber schwer).

type X => (number >= 50 && number > 60 || number = 70) || (string.startsWith("+"))

(dh normale Javascript-Anweisung / -Funktion kann hier verwendet werden, außer dass die Variablen durch den Typ ersetzt werden)

Nach meinem Verständnis ist moderne Sprache = normale Logik + Metalogik, wobei Metalogik = Codeprüfung + Codegenerierung ist. Viele Arbeiten versuchen einfach, die Metalogik-Syntax auf elegante Weise zusammenzuführen.

Vielleicht können wir irgendwie davon ausgehen, dass Range-Typen keine Zuckerart sind, sondern ein Kernkonzept?

// number literal extend `number` despite the fact 
// that `number` is not union of all number literals
type NumberLiteralIsNumber = 5 extends number ? true : false // true

// so if we will define Range (with some non-existing syntax which is clearly should be done in lib internals)
type Range<MIN extends number, MAX extends number> = MAX > MIN ? 5 and 2NaN : MAX === MIN ? MAX : MIN..MAX

// and assume that `number` is Range<-Infinity, +Infinity>
type NumberIsInfinitRange = number extends Range<-Infinity, +Infinity> ?
    Range<-Infinity, +Infinity> extends number ? true : false :
    false // true

// following things will be true
type AnyRangeIsNumber<T extends number, K extends Number> = 
    Range<T, K> extends number ? true : false // true
type NestedRangeIsNumber<T extends number, K extends number, S extends number> =
    Range<T, Range<K, S>> extends number ? true : false // true

Um dies zu vervollständigen, müssen wir in einigen Fällen Verhalten deklarieren

  1. Range<T, T> === T per Definition
  2. Range<5, 1> === NaN per Definition
  3. Range<NaN, T> === Range<T, NaN> === NaN da ein solcher Wert nicht existieren kann
  4. Range<1 | 2, 5> === Range<2, 5> die Möglichkeit einer größeren Zahl als Parameter des ersten Typs schränkt den Bereich ein, um kürzer zu sein
  5. Range<1, 4 | 5> === Range<1, 4> die Möglichkeit einer niedrigeren Zahl als zweiter Typparameter schränkt den Bereich ein, um kürzer zu sein
  6. 1.5 extends Range<1, 2> sollte per Definition wahr sein
  7. Range<Range<A, B>, C> === NaN extends Range<A, B> ? NaN : Range<B, C> folgt aus 5 und 3
  8. Range<A, Range<B, C>> === NaN extends Range<B, C> ? NaN : Range<A, B> folgt aus 6 und 3

Und ein wenig über Ranges innerhalb von Ranges:

type RangeIsInsideRange<T extends Range<any, any>, K extends Range<any, any>> = 
    T extends Range<infer A, infer B> 
        ? K extends Range<infer C, infer D> 
            ? NaN extends Range<A, C> 
                ? false 
                : NaN extends Range<B, D> 
                    ? false 
                    : true 
            : never
        : never

Bereichsuntertypen könnten sogar generisch definiert werden, als Vergleichsfunktion auf Typebene (a: T, b: T) => '<' | '=' | '>' .

...gibt es einen Vorschlag für die Verwendung regulärer JS-Funktionen auf Typebene?

Es gibt keinen Vorschlag per se, aber ich habe schon einmal einen schnellen Prototyp geschrieben . Leider gibt es große Bedenken hinsichtlich der IDE-Leistung und der Eingabebereinigung.

@yudinns Problem ist, dass Range zumindest unter diesem Namen existiert, daher kann es verwirrend sein, siehe: https://developer.mozilla.org/en-US/docs/Web/API/range Außerdem vermisse ich die Funktionalität, die ich definieren kann Zahlenreihen wie <1, n+2> - Schritt 2 ausgehend von einer oder einer komplizierteren Gleichung.

Dieser TC39-Vorschlag wird wahrscheinlich Stufe 4 erreichen, bevor dies umgesetzt wird.
Das Ticket ist 3 Jahre alt.

Vorschlag: Regex-validierter String-Typ: https://github.com/Microsoft/TypeScript/issues/6579

(Verwandte SO-Frage: https://stackoverflow.com/questions/3895478/does-javascript-have-a-method-like-range-to-generate-a-range-within-the-supp)

Es wäre auch cool, wenn Sie etwas wie number in range könnten, zB if 1 in 1..2 oder inklusive if 1 in 1..=2 rost macht das gut https://doc.rust-lang.org/reference/ expressions/range-expr.html . Das würde viel Platz sparen

Haben Python und Rost nicht bereits eine typisierte Reichweitenfunktion?
Können wir einfach bestehende Lösungen in anderen Sprachen neu implementieren, anstatt das Rad neu zu erfinden oder es mit Strings, Floats usw. Bei Bedarf können wir später mehr hinzufügen

Ich bin mehr als einmal darauf gestoßen und bin hierher gekommen, um nach einer Möglichkeit zu suchen, einen Typ zu definieren, der ein Bruch zwischen 0...1 ist - dies wird auf https://news.ycombinator.com/item?id= . diskutiert

@andrewphillipo Ich habe gesehen, dass dies auch diskutiert wird, und habe aus einer der Antworten zum Stack-Austausch den Begriff Unit Interval kennengelernt , der die richtige Art zu sein scheint, sich auf diesen spezifischen Bereich in einem generischen Kontext zu beziehen.

Wenn Bereiche als Typen jemals in Typoskript implementiert werden, könnte UnitInterval vielleicht als globaler Typ definiert werden, um eine gemeinsame Nomenklatur für etwas zu fördern, das wir häufig in der Programmierung verwenden.

Ja UnitInterval ist der richtige Name für den Bereich 0...1, also ist es richtig für den Typ! Die Benennung der Nummer ist immer noch nicht korrekt, daher wäre es ausgezeichnet, wenn dies verfügbar wäre, um unseren Code mit einem solchen Typ genauer zu beschreiben - wie funktioniert das unter der Haube in Rusts Typsystem - ist es nur eine Wache oder?

Wenn CantorSpace nicht zu weit ist, denke ich, dass dies eine angemessene Definition des "Bereichs" von "allen" reellen Zahlen zwischen [0, 1] wie er von einem Computer verstanden wird. Das Zuweisen dieser Werte zur Kompilierzeit kann nicht von einer unteren und oberen Grenze von Math.floor oder Math.ceil von Javascript abgeleitet werden, da Math.ceil(0) === 0 und Math.floor(1) === 1 unglücklich.

Wenn stattdessen die Menge aller Zahlen (0, 1) verwendet würde, würde es mit dem obigen Beispiel funktionieren, aber das ist irgendwie schlecht, Werte auszuschließen, die in der Alltagssprache als Prozentsätze bezeichnet werden. Wenn möglich, wäre es schön, wenn der Compiler die Grenzen irgendwie einbezieht, vielleicht durch eine strenge === Prüfung gegen die Randfälle 0 und 1.

oder vielleicht 1~~3 für Integer und 0.1~0.5 für Floats verwenden?

~ ist bereits vom unären bitweisen NOT- Operator belegt.

Warum wir über diese verdammten Schwimmer sprechen müssen, das Problem, das wir haben, sind ganze Zahlen. 99,99% ist ein Integer-Problem 0,001% ist ein Float-Problem.
Eine allgemeingültige Lösung ist nicht ohne weiteres möglich, also nehmen wir den Zahlenbereich von 0..10, wie er in den meisten Programmiersprachen zu sehen ist, wie er bereits vor Jahren implementiert wurde.

Hör auf zu reden

Vielleicht können wir die Rechnung aufteilen und diesen Vorschlag für den Fall int erweitern:
https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c

Und für Floats arbeite ich derzeit an einem anderen Designvorschlag, der auf einigen Ideen in dieser Ausgabe basiert (hauptsächlich @AnyhowStep; im Grunde die Möglichkeit, einen Open-Interval-Type zu haben).

Mach es einfach mit
x..y für ganze Zahlen.
0.1..2 würde einen Fehler ausgeben
1..2.1 würde auch einen Fehler ausgeben
Und jede nachweisbare nicht ganze Zahl.
Verdammt, kopiere die Implementierungslogik von
https://en.m.wikibooks.org/wiki/Ada_Programming/Types/range
Oder
https://kotlinlang.org/docs/reference/ranges.html
Oder
https://doc.rust-lang.org/reference/expressions/range-expr.html
und nennen Sie es einen Tag.

✅Das Rad muss nicht neu erfunden werden
✅Schwimmer müssen nicht berücksichtigt werden
✅Konsistenz in allen Sprachen
✅Getesteter und kopierfähiger stabiler Code aus bestehenden Quellen

Es lohnt sich, Floats zu lösen, da die "int"-basierte Typprüfung für Zahlen < 1000 trivial ist, indem der Typwert als Vereinigung jeder festen Ganzzahl 0..1000 hart codiert wird, so dass "keine Notwendigkeit" besteht, "re -das Rad erfinden" ist ein wenig ironisch. Warum nicht für einfache Anwendungsfälle das nutzen, was bereits funktioniert?

Ich würde lieber etwas Platz für eine Typdefinition sehen, die Dinge wie irrationale Zahlen versteht. Die Möglichkeit, Invarianten zur Kompilierzeit zu erkennen, wenn mit Radianten gearbeitet wird, wäre ein interessanter Anwendungsfall. Die Unterstützung von τ als Argument für eine Bereichswertgrenze ist ein gutes Ziel, um zu behaupten, dass diese Funktion wie beabsichtigt funktioniert.

Denken Sie daran, dass es in Javascript kein explizites integer . Alles ist nur ein number , das als Float gespeichert wird. Wenn der Bereichstypoperator implementiert ist, sollte er jeden Wert verarbeiten, den number kann.

@aMoniker abhängig von der Definition von explicit : BigInt

Sogar für Bigint gibt es Anwendungsfälle, die Union nicht lösen können, wie zum Beispiel sicherzustellen, dass ein Bigint in einen MySQL-signierten/unsignierten Bigint passt

Wie wäre es mit dem Erstellen eines Bereichstyps intrinsic für diese Funktion?

lib.es5.d.ts (oder lib.es2020.bigint.d.ts für die Unterstützung von Bigints)
type GreaterThan<N extends number | bigint> = intrinsic
type GreaterThanOrEqualTo<N extends number | bigint> = GreaterThan<N> | N
type LessThan<N extends number | bigint> = intrinsic
type LessThanOrEqualTo<N extends number | bigint> = LessThan<N> | N

/**
 * prevent `GreaterThan` and `LessThan` from desugaring
 * (in the same way that `number` does _not_ desugar to `-Infinity | ... | Infinity`)
 */
Benutzerland
type GreaterThanOrEqualTo2 = GreaterThanOrEqualTo<2>
type LessThan8 = LessThan<8>

type GreaterThanOrEqualTo2_And_LessThan8 = GreaterThanOrEqualTo2 & LessThan8
type LessThan2_Or_GreaterThanOrEqualTo8 = LessThan<2> | GreaterThanOrEqualTo<8> // inverse of `GreaterThanOrEqualTo2_And_LessThan8` (would be nice to be able to just do `Exclude<number | bigint, GreaterThanOrEqualTo2_And_LessThan8>` but that might be wishful thinking)
type LessThan2_And_GreaterThanOrEqualTo8 = LessThan<2> & GreaterThanOrEqualTo<8> // `never`
type GreaterThanOrEqualTo2_Or_LessThan8 = GreaterThanOrEqualTo2 | LessThan8 // `number | bigint`

type RangesAreNumbersOrBigIntsByDefault = LessThan8 extends number | bigint ? true : false // `true` (the user could always narrow this on a per-range basis, e.g. `LessThan8 & number`)
type RangesAcceptUnions = LessThan<7n | 7.5 | 8> // `LessThan<8>`
type RangesAcceptOtherRanges1 = LessThan<LessThan8> // `LessThan8`
type RangesAcceptOtherRanges2 = LessThan<GreaterThanOrEqualTo2> // `number | bigint`
type RangesSupportBeingInAUnion = (-6 | 0.42 | 2n | 2 | 3) | LessThan<2> // `LessThan<2> | 2n | 2 | 3`
type RangesSupportBeingInAnIntersection = (-6 | 0.42 | 2n | 2 | 3) & LessThan<2> // `-6 | 0.42`
type RangesSupportBeingInAUnionWithOtherRanges = LessThan<2> | LessThan8 // `LessThan8`
type RangesSupportBeingInAnIntersectionWithOtherRanges = LessThan<2> & LessThan8 // `LessThan<2>`

@RyanCavanaugh

Dies erfordert wirklich einige überzeugende Anwendungsfälle, da die Implementierung von Gewerkschaften heute völlig ungeeignet ist, um Gewerkschaften dieser Größe zu verwirklichen. Etwas, das passieren würde, wenn Sie so etwas schreiben würden ...

Folgendes wird ohne int-Typ nicht korrekt sein:

const arr: string[] = ['a', 'b', 'c']
const item = arr[1.01]  // Typescript inferred this as string but actually it is undefined
console.log(item)  // Will print undefined, we miss inferred the type

Der folgende Typ verhindert dieses Problem:

type TUInt = 0..4294967295;

Ein weiterer Anwendungsfall für die Eingabe von Int32 und Int64 ist, wenn Leute anfangen, ihren Code damit zu kommentieren ... die Tür zu einer besseren Interoperabilität mit anderen Sprachen öffnen ... fast alle statisch typisiert Sprachen haben einen Integer-Typ: Java, C#, C, Rust, F#, Go ... etc.

Wenn ich beispielsweise eine in TypeScript geschriebene npm-Bibliothek aus C# aufrufen möchte, gibt es Bibliotheken, die TypeScript-Definitionen übernehmen und eine Schnittstelle für mich in C# erstellen, aber das Problem ist, dass der Typ number float die nicht verwendet werden kann, um ein Array in C# zu indizieren, ohne es in Großbuchstaben zu schreiben ... etc.

Andere Anwendungsfälle: einfachere Übertragung zwischen Sprachen, Leistungsoptimierung ... etc

Beachten Sie, dass ein weiterer Fall, in dem dies nützlich wäre, die Interaktion mit WebAssembly ist, das sehr explizite separate Zahlentypen hat (ich denke, es wurde kurz als C#-Anwendungsfall erwähnt, wollte aber klarstellen, dass es viel weiter gefasst ist).

UPD: Nvm, ich sehe, es wurde in https://github.com/microsoft/TypeScript/issues/15480#issuecomment -365420315 erwähnt, das Github "hilfreich" als "versteckte Elemente" versteckt hat.

+1

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen