Typescript: Vorschlag: non-nullable type

Erstellt am 22. Juli 2014  ·  358Kommentare  ·  Quelle: microsoft/TypeScript

Einführung von zwei neuen Syntaxen für die Typdeklaration basierend auf JSDoc

var myString: !string = 'hello world'; //non-nullable
var myString1: ?string = 'hello world'; // nullable 
var myString2: string = 'hello world'; // nullable 
var myString3 = 'hello world'; // nullable

standardmäßig sind der Typ nullable.

Zwei neue Compiler-Flags:

  • inferNonNullableType den Compiler einen nicht-nullbaren Typ ableiten:
var myString3 = 'hello world' // typeof myString is '!string', non-nullable
  • nonNullableTypeByDefault (ich denke, es könnte einen besseren Namen geben) :
var myString: !string = 'hello world'; // non-nullable
var myString1: string = 'hello world'; // non-nullable 
var myString2: ?string = 'hello world'; // nullable 
var myString3 = 'hello world' // non-nullable
Committed Fixed Suggestion

Hilfreichster Kommentar

Es tut mir leid, wenn ich ein geschlossenes Problem kommentiere, aber ich kenne keinen besseren Ort, an dem ich nachfragen kann, und ich denke nicht, dass dies eine neue Ausgabe wert ist, wenn kein Interesse besteht.
Wäre es möglich, implizite Nullen auf Dateibasis zu behandeln?
Behandeln Sie zum Beispiel eine Reihe von td-Dateien mit noImplicitNull (weil sie von definitivtyped stammen und so konzipiert wurden), aber meine Quelle als implicitNull behandeln?
Würde das jemand nützlich finden?

Alle 358 Kommentare

Ich schlage vor, einen anderen Typ als String als Beispiel zu verwenden, da er von Natur aus nullfähig ist. :P
Ich kann erkennen, dass nicht-nullbare Typen problematisch sind, da der Benutzer und der Compiler von "!" erwartet, dass der Typ immer ungleich null ist, was in JavaScript nie wirklich behauptet werden kann. Ein Benutzer könnte so etwas definieren:

function(myNonNull:!myClass):void {
  myNonNull.foo();
}

Und weil es als nicht-null definiert ist, könnte alles für den Compiler glücklich sein, aber dann übergibt jemand anderes, das es in Javascript verwendet, etwas null und kaboom.

Nun, dies könnte vielleicht die Lösung sein, dass für öffentlich zugängliche Methoden automatisch nicht null behauptet werden könnte. Aber dann könnte der Compiler auch behaupten, dass Sie keine öffentlichen Eigenschaften (oder wirklich private) haben können, die eine !nonnull-Deklaration haben können, da sie nicht erzwungen werden könnten.

Dies kann tiefer in die Diskussion von Codeverträgen einfließen, damit dies ordnungsgemäß durchgesetzt wird.

Verzeihen Sie meinen Kritikern, ich denke, es besteht sehr wenig Bedarf an nicht-nullbaren Typen, wenn/sobald-algebraische Datentypen vorhanden sind. Der Grund, warum Leute null , um einen fehlenden Wert darzustellen, ist, dass es in JavaScript und in den meisten OOP-Sprachen keinen besseren Weg gibt, dies zu tun. Bildgebungs-ADTs sind also bereits da. Dann, was die alten Libs betrifft, die vor Nicht-Nullablen geschrieben wurden, wird es das Leben nicht besser machen, sie zu haben. Was die neuen Bibliotheken betrifft, so kann man mit ADTs sehr genau modellieren, was ein Wert gemäß der Spezifikation der Geschäftsdomäne annehmen kann, ohne überhaupt Nullen zu verwenden. Ich denke also, was ich sagen will, ist, dass ADT ein viel leistungsfähigeres Werkzeug ist, um dasselbe Problem zu lösen.

Persönlich habe ich gerade eine kleine Maybe<T> Schnittstelle geschrieben und bin diszipliniert, um sicherzustellen, dass Variablen dieses Typs niemals null sind.

Ich schlage vor, einen anderen Typ als String als Beispiel zu verwenden, da er von Natur aus nullfähig ist. :P
Ich kann erkennen, dass nicht-nullbare Typen problematisch sind, da der Benutzer und der Compiler von "!" erwartet, dass der Typ immer ungleich null ist, was in JavaScript nie wirklich behauptet werden kann. Ein Benutzer könnte so etwas definieren:

function(myNonNull:!myClass):void {
myNonNull.foo();
}
Und weil es als nicht-null definiert ist, könnte alles für den Compiler glücklich sein, aber dann übergibt jemand anderes, das es in Javascript verwendet, etwas null und kaboom.

Ich verstehe nicht wirklich, dass man auch definieren kann:

function myFunc(str: string): int {
 return str && str.length;
}

und wenn jemand ein int an diese Funktion übergibt, wird es auch mit einem Fehler enden -nullable-Typ scheint mir vernünftig. Übrigens machen SaferTypeScript und ClosureCompiler solche Prüfungen bereits.

Bei Unionstypen könnten wir dafür eine ziemlich einfachere Spezifikation haben.
Nehmen wir an, wir haben jetzt einen Basistyp 'null', wir können einen 'strengeren' Modus haben, in dem 'null' und 'undefined' mit keinem Typ kompatibel sind. Wenn wir also einen nullbaren Wert ausdrücken möchten, würden wir Folgendes tun:

var myNullableString: (null | string);
var myString = "hello";
myNullableString = myString //valid
myString = myNullableString // error null is not assignable to string;

Bei aktiviertem 'strict mode' sollte Typescript prüfen, ob alle Variablen, die nicht nullbar sind, initialisiert sind, auch optionale Parameter sind standardmäßig nullbar.

var myString: string; // error
var myNullableString: (null | string); // no error

function myString(param1: string, param2?: string) {
  // param1 is string
  // param2 is (null | string)
}

@fdecampredon +1

IIRC von dem, was Facebook von Flow gezeigt hat, das TypeScript-Syntax verwendet, aber standardmäßig mit nicht nullbaren Typen eine Abkürzung für (null | T) wie in Ihrem ursprünglichen Beitrag - ich denke, es war ?T oder T? .

var myString: string; // error

Das kann möglicherweise ziemlich ärgerlich sein, wenn Sie eine Variable bedingt initialisieren möchten, z.

var myString: string;
if (x) {
myString = a;
} else if (y) {
myString = b;
} else {
myString = c;
}

In Rust ist dies zum Beispiel in Ordnung, solange der Compiler sehen kann, dass myString vor seiner Verwendung initialisiert wird, aber die Inferenz von TypeScript unterstützt dies im Moment nicht.

Ehrlich gesagt, so etwas wie var myString = '' statt var myString: string , stört mich nicht so sehr, aber sicher ist diese Art von Regel immer möglich.

@fdecampredon +1 dafür - die Idee gefällt mir sehr gut. Für Codebasen, die zu 100 % aus JavaScript bestehen, wäre dies eine nützliche Einschränkung nur für die Kompilierungszeit. (Wie ich Ihren Vorschlag verstehe, besteht keine Absicht, dass generierter Code dies erzwingt?)

Was die Abkürzung für (null | string) angeht, ist ?string sicher in Ordnung.
Und sicher @johnnyreilly, es ist nur eine Überprüfung der Kompilierzeit

Sum-Typen machen Nicht-Nullable-Typen standardmäßig zu einer sehr interessanten Möglichkeit. Die Sicherheitseigenschaften von non-nullable können standardmäßig nicht überbewertet werden. Summentypen plus das geplante "if/typeof Destructuring" (nicht sicher, wie dies heißen soll) machen es sogar typsicher, NULL-fähige und Nicht-Null-fähige APIs zu integrieren.

Typen standardmäßig nicht-nullierbar zu machen, ist jedoch eine große grundlegende Änderung, die eine Änderung fast jeder vorhandenen Typdefinitionsdatei von Drittanbietern erfordern würde. Obwohl ich zu 100 % für den Breaking Change bin, ist niemand in der Lage, die Typendefinitionen zu aktualisieren, die es da draußen gibt.

Es ist gut, dass im DefinitelyTyped-Repository ein großer Konsens dieser Definitionen gesammelt wird, aber ich habe immer noch praktische Bedenken bezüglich dieser Funktion.

@samwgoldman die Idee ist, Typen ohne NULL-Werte nur unter einem speziellen Compiler-Flag wie nonImplicitAny dieses Flag könnte strict oder nonNullableType heißen. Es würde also keine Breaking Changes geben.

@fdecampredon Was ist mit den Typdefinitionen für Nicht-TypeScript-Bibliotheken, wie die bei DefinitelyTyped? Diese Definitionen werden vom Compiler nicht überprüft, daher müsste jeder Code von Drittanbietern, der null zurückgeben könnte, neu annotiert werden, um richtig zu funktionieren.

Ich kann mir eine Typdefinition für eine Funktion vorstellen, die derzeit als "returns string" annotiert ist, aber manchmal null zurückgibt. Wenn ich in meinem nonNullableType Code von dieser Funktion abhängig bin, beschwert sich der Compiler nicht (wie könnte er?) und mein Code ist nicht mehr nullsicher.

Sofern ich nichts übersehe, glaube ich nicht, dass dies eine Funktion ist, die mit einem Flag ein- und ausgeschaltet werden kann. Mir scheint, dass dies eine semantische Alles-oder-Nichts-Änderung ist, um die Interoperabilität zu gewährleisten. Ich würde mich jedoch freuen, wenn mir das Gegenteil bewiesen wird, da ich denke, dass ein Flag-Switching-Feature wahrscheinlicher ist.

Abgesehen davon sind noch nicht viele Informationen zum Flow-Compiler von Facebook verfügbar, aber aus der Videoaufzeichnung der Präsentation scheint es, als ob sie standardmäßig nicht nullbar waren. Wenn ja, gibt es hier zumindest einen Vorrang.

Ok, nehmen wir an, es gibt eine Abkürzung ? type für type | null | undefined .

@fdecampredon Was ist mit den Typdefinitionen für Nicht-TypeScript-Bibliotheken, wie die bei DefinitelyTyped? Diese Definitionen werden vom Compiler nicht überprüft, daher müsste jeder Code von Drittanbietern, der null zurückgeben könnte, neu annotiert werden, um richtig zu funktionieren.

Ich kann mir eine Typdefinition für eine Funktion vorstellen, die derzeit als "returns string" annotiert ist, aber manchmal null zurückgibt. Wenn ich in meinem nichtNullableType'ed Code von dieser Funktion abhängig bin, beschwert sich der Compiler nicht (wie könnte er?) und mein Code ist nicht mehr nullsicher.

Ich sehe das Problem nicht, sicher sind einige Definitionsdateien im nonNullableType Modus nicht gültig, aber die meiste Zeit vermeidet eine gute Bibliothek, null oder undefined daher wird die Definition in den meisten Fällen immer noch richtig sein.
Wie auch immer, ich persönlich kann selten eine DefinitelyTyped-Definition auswählen, ohne sie überprüfen/ändern zu müssen. Sie haben nur ein wenig zusätzliche Arbeit, um einigen Definitionen ein ? Präfix hinzuzufügen.

Sofern ich nichts übersehe, glaube ich nicht, dass dies eine Funktion ist, die mit einem Flag ein- und ausgeschaltet werden kann. Mir scheint, dass dies eine semantische Alles-oder-Nichts-Änderung ist, um die Interoperabilität zu gewährleisten. Ich würde mich jedoch freuen, wenn mir das Gegenteil bewiesen wird, da ich denke, dass ein Flag-Switching-Feature wahrscheinlicher ist.

Ich verstehe nicht, warum wir keine Flag-Switching-Funktion haben könnten, die Regeln wären einfach:

  • im normalen Modus entspricht ? string string und null oder undefined sind allen Typen zuweisbar
  • im nonNullableType-Modus entspricht ? string string | null | undefined und null oder undefined keinem anderen Typ als null oder undefined

Wo ist die Inkompatibilität mit einem Flag-Switched-Feature?

Flags, die die Semantik einer Sprache ändern, sind eine gefährliche Sache. Ein Problem besteht darin, dass die Auswirkungen möglicherweise sehr nicht lokal sind:

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

Es ist wichtig, dass jemand, der sich ein Stück Code ansieht, dem Typsystem "folgen" kann und die daraus gezogenen Schlussfolgerungen versteht. Wenn wir anfangen, eine Reihe von Flaggen zu haben, die die Regeln der Sprache ändern, wird dies unmöglich.

Die einzig sichere Methode besteht darin, die Semantik der Zuweisung gleich zu halten und zu ändern, was ein Fehler ist und was nicht von einem Flag abhängt, ähnlich wie es heute bei noImplicitAny funktioniert.

Ich weiß, dass es die Retro-Kompatibilität beeinträchtigen würde, und ich verstehe @RyanCavanaugh , aber nachdem ich Flowtype wirklich ein unschätzbares Feature ist, hoffe ich, dass es am Ende ein Teil des Typoskripts wird

Zusätzlich zu RyanCavanaughs Kommentar --> Von dem, was ich irgendwo gelesen habe, erwähnt die ES7-Spezifikation / der Vorschlag die Verwendung von Funktionsüberladungen (gleicher Funktionsname, aber anderer Eingabeparameter-Datentyp). Das ist eine sehr dringend benötigte Funktion für Javascript.

Aus den Flow-Dokumenten :

Flow betrachtet null als eindeutigen Wert, der zu keinem anderen Typ gehört

var o = null;
print(o.x); // Error: Property cannot be accessed on possibly null value

Jeder Typ T kann so eingestellt werden, dass er null (und den zugehörigen Wert undefiniert) enthält, indem man ?T . schreibt

var o: ?string = null;
print(o.length); // Error: Property cannot be accessed on possibly null or undefined value

[Flow] versteht die Auswirkungen einiger dynamischer Typtests

(dh in der TS-Sprache versteht Typwächter)

var o: ?string = null;
if (o == null) {
  o = 'hello';
}
print(o.length); // Okay, because of the null check

Einschränkungen

  • Prüfungen von Objekteigenschaften sind aufgrund der Aliasing-Möglichkeit eingeschränkt:

Neben der Möglichkeit, Typen lokaler Variablen anzupassen, kann Flow manchmal auch Typen von Objekteigenschaften anpassen, insbesondere wenn zwischen einer Prüfung und einer Verwendung keine Zwischenoperationen liegen. Im Allgemeinen schränkt das Aliasing von Objekten jedoch den Umfang dieser Argumentation ein, da eine Überprüfung einer Objekteigenschaft durch ein Schreiben in diese Eigenschaft über einen Alias ​​ungültig werden kann und es für eine statische Analyse schwierig ist, Aliase genau zu verfolgen

  • Typwächterprüfungen können für Objekteigenschaften überflüssig sein.

[D]erwarten Sie nicht, dass ein NULL-fähiges Feld in einer Methode als nicht NULL erkannt wird, da in einer anderen Methode in Ihrem Code eine NULL-Prüfung durchgeführt wird, selbst wenn Ihnen klar ist, dass die NULL-Prüfung für die Sicherheit ausreichend ist Laufzeit (zB weil Sie wissen, dass Aufrufe der erstgenannten Methode immer auf Aufrufe der letztgenannten Methode folgen).

  • undefined wird nicht überprüft.

Undefinierte Werte können ebenso wie null Probleme verursachen. Leider sind undefinierte Werte in JavaScript allgegenwärtig und es ist schwer, sie zu vermeiden, ohne die Benutzerfreundlichkeit der Sprache ernsthaft zu beeinträchtigen. Arrays können beispielsweise Löcher für Elemente aufweisen; Objekteigenschaften können dynamisch hinzugefügt und entfernt werden. Flow geht in diesem Fall einen Kompromiss ein: Es erkennt undefinierte lokale Variablen und Rückgabewerte, ignoriert jedoch die Möglichkeit von Undefinierten, die aus Objekteigenschaften- und Arrayelementzugriffen resultieren

Was ist, wenn die Option gleichzeitig mit der Einführung des Typs null (und der Kurzform des Fragezeichens) hinzugefügt wird? Das Vorhandensein eines null Typs in einer Datei würde den Compiler in den Nicht-Null-Modus für diese Datei zwingen, selbst wenn das Flag nicht in der Befehlszeile vorhanden ist. Oder ist das ein bisschen zu magisch?

@jbondc scheint gut zu sein. Das Problem dabei ist jedoch, dass es überall mit ! enden wird :p

It's tempting to want to change JavaScript but the reality is a 'string' is nullable or can be undefined.

Was bedeutet das? Es gibt keine statischen Typen in js. Also, ja, Strings sind "nullable", aber vergessen wir nicht, dass sie auch numerierbar und anstößig und fooable usw. sind. Jeder Wert kann jeden Typ haben.

Wenn Sie also ein statisches Typsystem über Javascript schichten, ist die Entscheidung, ob statische Typen nullable sind oder nicht, nur eine Entwurfsentscheidung. Mir scheint, dass nicht-nullbare Typen ein besserer Standard sind, da Sie normalerweise nur in besonderen Fällen möchten, dass eine Funktionssignatur beispielsweise einen Nullwert zusätzlich zum angegebenen Typ akzeptiert.

Direktiven wie "use strict", die bereichsbezogene Änderungen an der Semantik bewirken, sind bereits Teil der Sprache; Ich denke, es wäre vernünftig, in TypeScript eine Direktive "use nonnullable types" zu haben.

@metaweta Ich denke nicht, dass es ausreicht, was zum Beispiel passiert, wenn ein _non-null-Modul_ ein nullables konsumiert:

//module A
export function getData(): string[] {
  return null;
}
//module B
'use nonnull'
import A = require('./A');

var data: string[] = A.getData();

data in Modul B ist tatsächlich nullable, aber da 'use nonnull' in Modul A nicht verwendet wurde, sollten wir einen Fehler melden?
Ich sehe keine Möglichkeit, dieses Problem mit richtlinienbasierten Funktionen zu lösen.

Ja,

var data: string[] = A.getData();

würde einen Fehler verursachen. Stattdessen müssten Sie einen Standardwert angeben, wenn getData() null zurückgibt:

var data: string[] = A.getData() || [];

@metaweta ok, aber woher weißt du, dass es ein Fehler ist? :)
Typ von getData ist immer noch '() => string[]' würden Sie automatisch alles, was von einem 'nullable module' kommt, als 'nullable' behandeln?

Ja, genau (es sei denn, ein Typ aus dem NULL-fähigen Modul ist explizit anders gekennzeichnet).

Das hört sich so an, als ob Sie jetzt ein Flag pro Datei möchten, das vorschreibt, ob diese Datei standardmäßig auf null setzbar ist oder nicht.

Persönlich denke ich, dass es ein bisschen spät ist, diese Änderung einzuführen, und @RyanCavanaugh hat Recht, die Änderung würde Typescript weniger vorhersehbar machen, da Sie nicht in der Lage wären, nur durch Betrachten einer Datei festzustellen, was vor sich ging.

Starten Projekte mit diesem Compiler-Flag standardmäßig ein oder aus? Wenn jemand an einem Nullable-Standardprojekt arbeitet und ein Nullable-Standardprojekt erstellt/umschaltet, wird das zu Verwirrung führen?
Ich arbeite derzeit in den meisten meiner Projekte mit No Implicit Any, und jedes Mal, wenn ich auf ein Projekt stoße, bei dem diese Option nicht aktiviert ist, überrascht es mich.

Das No Impicit Any ist gut, aber in Bezug auf Flags, die das Verhalten der Sprache ändern, denke ich, dass dies die Linie sein sollte. Darüber hinaus werden Menschen, die an mehreren Projekten arbeiten, die von verschiedenen Personen mit unterschiedlichen Regeln gestartet wurden, aufgrund falscher Annahmen und Ausrutscher viel Produktivität verlieren.

@RyanCavanaugh war besorgt über Nicht-Lokalität, und Direktiven sind lexikalisch abgegrenzt. Sie können nicht mehr lokaler werden, es sei denn, Sie kommentieren jede Site. Ich bin nicht besonders für die Richtlinie; Ich habe nur darauf hingewiesen, dass die Option existiert und mindestens so vernünftig ist wie "use strict" in ES5. Ich persönlich bin standardmäßig für Typen ohne Nullwerte, aber praktisch ist es dafür zu spät. Angesichts dieser Einschränkungen bin ich für die Verwendung von ! irgendwie. Mit dem Vorschlag von

Es tut mir leid, wenn ich nicht klar war, ich stimmte Ryan zu und fügte meine eigenen Bedenken hinzu.

Ehrlich gesagt, wenn das Hinzufügen von use not-null der Preis für die Vermeidung aller Null-Zeiger-Ausnahmen ist, würde ich es ohne Probleme bezahlen, wenn man bedenkt, dass null oder undefined jedem Typ zuweisbar sind, ist dies der schlimmste Fehler das meiner Meinung nach gemachte Typoskript.

@jbondc Ich habe 'use strict' nicht verwendet und mache daher einige Annahmen. Bitte korrigiere mich, wenn meine Annahmen falsch sind:

Nicht null beeinflusst nicht die Syntax, die der Programmierer schreibt, sondern die Fähigkeiten des nächsten Programmierers, der versucht, diesen Code zu verwenden (vorausgesetzt, dass Ersteller und Benutzer getrennte Personen sind).

Also der Code:

function myfoo (mynumber: number) {
    return !!mynumber;
} 

(auf einem Telefon tippen, kann also falsch sein)
Ist gültiger Code sowohl in einem normalen Projekt als auch in einem Nichtnull-Projekt. Der Programmierer kann nur erkennen, ob der Code funktioniert oder nicht, wenn er sich die Befehlszeilenargumente ansieht.

Bei der Arbeit haben wir ein Testprojekt (das das Prototyping neuer Funktionen umfasst) und ein Hauptprojekt (mit unserem aktuellen Code). Wenn die Prototypen bereit sind, von einem Projekt in ein anderes verschoben zu werden (normalerweise bei großen Refraktoren), treten keine Fehler im Code auf, sondern Fehler bei der Verwendung des Codes. Dieses Verhalten unterscheidet sich von keinem impliziten und verwendet strikte, was beide sofort einen Fehler verursachen würde.

Jetzt habe ich einen gewissen Einfluss auf diese Projekte, daher kann ich die Verantwortlichen warnen, dieses neue "Feature" nicht zu verwenden, da es keine Zeit sparen würde, aber ich habe nicht diese Kapazität für alle Projekte bei arbeiten.
Wenn wir diese Funktion auch nur in einem Projekt aktivieren möchten, müssen wir sie in allen unseren anderen Projekten aktivieren, da wir eine sehr große Menge an Code-Sharing und Code-Migration zwischen Projekten haben und diese "Funktion" uns viel verursachen würde Zeit zurückgehen und bereits fertige Funktionen 'reparieren'.

Richtig @jbondc. @Griffork : Tut mir leid, dass ich dieses Missverständnis nicht verstanden habe; Eine Direktive ist ein literaler Zeichenfolgenausdruck, der als erste Zeile einer Programm- oder Funktionsproduktion erscheint und deren Auswirkungen auf diese Produktion beschränkt sind.

"use not-null";
// All types in this program production (essentially a single file) are not null

gegen

function f(n: number) {
  "use not-null";
  // n is not null and local variables are not null
  function g(s: string) {
    // s is not null because g is defined in the scope of f
    return s.length;
  }
  return n.toFixed(2);
}

function h(n: number) {
  // n may be null
  if (n) { return n.toFixed(3); }
  else { return null; }
}

Typen ohne Nullwerte sind nutzlos. Typen ohne Nullwerte sind nutzlos. Sie sind nutzlos. Nicht zu gebrauchen! Sie wissen es nicht, aber Sie brauchen sie nicht wirklich. Es macht wenig Sinn, sich selbst einzuschränken, indem wir verkünden, dass wir von nun an keine NULL-Werte mehr verwenden werden. Wie würden Sie einen fehlenden Wert darstellen, beispielsweise in einer Situation, in der Sie versuchen, eine Teilzeichenfolge zu finden, die nicht vorhanden ist? Wenn Sie einen fehlenden Wert nicht ausdrücken können (was NULL jetzt vorerst tut), wird Ihr Problem nicht gelöst. Sie werden überall eine harte Welt mit NULL-Werten gegen eine ebenso harte Welt ohne fehlende Werte eintauschen. Was Sie wirklich brauchen, sind sogenannte algebraische Datentypen , die (neben vielen anderen coolen Dingen) die Fähigkeit besitzen, einen fehlenden Wert darzustellen (was Sie in erster Linie suchen und was in der imperativen Welt durch NULL dargestellt wird). Ich bin strikt dagegen , der Sprache Nicht-Null-Werte hinzuzufügen, weil es wie nutzloser syntaktischer/semantischer Müll aussieht, der eine naive und umständliche Lösung für ein bekanntes Problem darstellt. Bitte lesen Sie über Optionals in F# und Maybe in Haskell sowie über Varianten (auch bekannt als Tagged Unions, Discriminated Unions) und Mustervergleich.

@aleksey-bykov Es klingt, als wüssten Sie nicht, dass JavaScript zwei Nullwerte hat, undefined und null . Der null in JavaScript wird nur bei einem nicht übereinstimmenden Regexp und beim Serialisieren eines Datums in JSON zurückgegeben. Der einzige Grund, warum es überhaupt in der Sprache ist, war die Interaktion mit Java-Applets. Variablen, die deklariert, aber nicht initialisiert wurden, sind undefined , nicht null . Fehlende Eigenschaften eines Objekts geben undefined , nicht null . Wenn undefined explizit ein gültiger Wert sein soll, können Sie propName in obj testen. Verwenden Sie obj.hasOwnProperty(propName) wenn Sie prüfen möchten, ob eine Eigenschaft für das Objekt selbst existiert und nicht, ob sie vererbt wird. Fehlende Teilstrings geben -1 zurück: 'abc'.indexOf('d') === -1 .

In Haskell ist Maybe gerade deshalb nützlich, weil es keinen universellen Untertyp gibt. Der untere Typ von Haskell steht für Nicht-Terminierung, nicht für einen universellen Untertyp. Ich stimme zu, dass algebraische Datentypen benötigt werden, aber wenn ich möchte, dass ein Baum mit Ganzzahlen beschriftet ist, möchte ich, dass jeder Knoten eine Ganzzahl hat, nicht null oder undefined . Wenn ich die haben möchte, verwende ich einen Baum mit der Aufschrift Maybe int oder einen Reißverschluss.

Wenn wir eine "use not-null" -Direktive annehmen, möchte ich auch "use not-void" (weder null noch undefiniert).

Wenn Sie Ihren eigenen Code vor Nullen schützen möchten, verbieten Sie einfach die Null
Literale. Es ist viel einfacher, als Typen ohne Nullwerte zu entwickeln. Undefiniert ist a
etwas komplizierter, aber wenn du weißt woher sie kommen dann
Sie wissen, wie man sie vermeidet. Bottom in Haskell ist von unschätzbarem Wert! Ich wünsche
JavaScript (TypeScript) hatte einen globalen Supertyp ohne Wert. ich vermisse es
schlecht, wenn ich einen Ausdruck einwerfen muss. Ich habe TypeScript verwendet
seit v 0.8 und nie Nullen verwendet, geschweige denn brauchte sie. Einfach ignorieren
sie wie bei jeder anderen nutzlosen Sprachfunktion wie with
Erklärung.

@aleksey-bykov Wenn ich eine Bibliothek schreibe und sicherstellen möchte, dass Eingaben nicht null sind, muss ich überall Laufzeittests dafür durchführen. Ich möchte Tests zur Kompilierzeit dafür, es ist nicht schwer bereitzustellen, und sowohl Closure als auch Flow bieten Unterstützung für nicht-null/undefinierte Typen.

@metaweta , Sie können sich nicht von Nullen garantieren. Bevor Ihr Code kompiliert wird, gibt es unzählige Möglichkeiten, Ihre Bibliothek zum Weinen zu bringen: pleaseNonNullablesNumbersOnly(<any> null) . Nach der Kompilierung in js gibt es überhaupt keine Regeln. Zweitens, warum sollte es Sie interessieren? Sagen Sie es im Voraus laut und deutlich nulls are not supported, you put a null you will get a crash , wie bei einem Haftungsausschluss können Sie sich nicht von allen möglichen Leuten da draußen garantieren, aber Sie können Ihren Verantwortungsbereich umreißen. Drittens kann ich mir kaum eine große Mainstream-Bibliothek vorstellen, die für jeden Benutzer kugelsicher ist, aber sie ist immer noch wahnsinnig beliebt. Ist Ihre Anstrengung also Mühe wert?

@aleksey-bykov Wenn die Clients meiner Bibliothek auch einer Typprüfung unterzogen werden, kann ich sicherlich garantieren, dass ich keine Null bekomme. Das ist der springende Punkt von TypeScript. Nach Ihrer Argumentation sind Typen überhaupt nicht erforderlich: Sagen Sie einfach in Ihrer Dokumentation laut und deutlich, was der erwartete Typ ist.

Außerhalb des Themas sind Nullen für uns äußerst wertvoll, da deren Überprüfung schneller ist als der Vergleich mit undefined.
Obwohl wir sie nicht überall verwenden, versuchen wir, sie nach Möglichkeit zu verwenden, um nicht initialisierte Werte und fehlende Zahlen darzustellen.

Zum Thema:
Wir hatten noch nie ein Problem damit, dass Nullen in anderen Code "entkommen", aber wir hatten Probleme mit zufälligen Undefinierten oder NaNs. Ich glaube, dass in diesem Szenario eine sorgfältige Codeverwaltung besser ist als ein Flag.

Für Bibliothekstypisierungen wäre es jedoch schön, den redundanten Typ null zu haben, damit wir Funktionen annotieren können, die null zurückgeben können (dies sollte nicht vom Compiler, sondern durch Codierungspraktiken erzwungen werden).

@metaweta , nach meiner Überlegung sollten Ihre Clients keine Nullen in ihrer Codebasis verwenden. Zu klobig? Fügen Sie einen Compiler-Schalter --noNullLiteral für Phantasie hinzu. Alles andere bleibt intakt, gleicher Code, keine Sorgen, viel leichtere Lösung mit minimalem Platzbedarf. Zurück zu meinem Punkt, nehmen Sie an, dass Ihre Typen, die nicht auf Null setzbar sind, den Weg zu TS gefunden haben und in 2 verschiedenen Geschmacksrichtungen erhältlich sind:

  • man kann die ! Syntax verwenden, um einen Typ anzugeben, der keine Null annehmen kann, zum Beispiel kann string! keine Null annehmen
  • noNullsAllowed-Schalter ist eingeschaltet

dann bekommst du ein Stück Json von deinem Server über Ajax mit Nullen überall, Moral: Die dynamische Natur von Javascript kann nicht durch eine Typannotation darüber korrigiert werden

@aleksey-bykov Wenn ich ein Objekt mit einer numerischen Eigenschaft x erwarte und {"x":"foo"} vom Server erhalte, kann das Typsystem dies nicht verhindern . Das ist zwangsläufig ein Laufzeitfehler und ein unvermeidliches Problem, wenn auf dem Server etwas anderes als TypeScript verwendet wird.

Wenn der Server jedoch in TypeScript geschrieben ist und auf einem Knoten ausgeführt wird, kann er in Gegenwart einer .d.ts-Datei für meinen Front-End-Code transpiliert werden und die Typprüfung garantiert, dass der Server niemals JSON mit Nullen sendet darin oder ein Objekt, dessen Eigenschaft x keine Zahl ist.

@metaweta , Typen ohne Typsicherheitsmaßnahme , das

Ja, das brauchen wir dringend. Siehe Der Milliarden-Dollar-Fehler von Hoare. Die Null-Zeiger-Ausnahme (NPE) ist der häufigste Fehler, der in typisierten Programmiersprachen auftritt, die nullbare von nicht-nullbaren Typen unterscheiden. Es ist so üblich, dass Java 8 Optional hinzufügte, um verzweifelt zu versuchen, es zu bekämpfen.

Die Modellierung von Nullables im Typsystem ist nicht nur ein theoretisches Anliegen, sondern eine enorme Verbesserung. Selbst wenn Sie in Ihrem Code große Sorgfalt darauf verwenden, Nullen zu vermeiden, können die von Ihnen verwendeten Bibliotheken dies nicht tun, und daher ist es nützlich, ihre Daten mit dem Typsystem richtig modellieren zu können.

Da es nun Unions und Type Guards in TS gibt, ist das Type-System mächtig genug, um dies zu tun. Die Frage ist, ob dies abwärtskompatibel möglich ist. Persönlich halte ich diese Funktion für wichtig genug, damit TypeScript 2.0 in dieser Hinsicht abwärtskompatibel ist.

Die ordnungsgemäße Implementierung dieser Funktion weist wahrscheinlich auf Code hin, der bereits beschädigt ist, anstatt vorhandenen Code zu beschädigen: Es zeigt einfach auf die Funktionen, die NULL-Werte außerhalb von ihnen verlieren (höchstwahrscheinlich unbeabsichtigt) oder auf Klassen, die ihre Member nicht richtig initialisieren (dieser Teil) ist schwieriger, da das Typsystem möglicherweise die Initialisierung von Memberwerten in den Konstruktoren berücksichtigen muss).

Hier geht es nicht darum, Nullen _nicht zu verwenden_. Es geht darum, alle beteiligten Typen richtig zu modellieren. Tatsächlich würde diese Funktion die Verwendung von Nullen auf sichere Weise ermöglichen - es gäbe keinen Grund mehr, sie zu vermeiden! Das Endergebnis wäre dem Mustervergleich bei einem algebraischen Maybe Typ sehr ähnlich (außer dass es mit einer if-Prüfung statt mit einem case-Ausdruck durchgeführt würde).

Und hier geht es nicht nur um Nullliterale. null und undefined sind strukturell gleich (es gibt keine Funktionen/Operatoren, die auf dem einen aber nicht auf dem anderen funktionieren), daher könnten sie mit einem einzigen Nulltyp in TS ausreichend gut modelliert werden.

@metaweta ,

Der Nullwert in JavaScript wird nur bei einem nicht übereinstimmenden Regexp und beim Serialisieren eines Datums in JSON zurückgegeben.

Überhaupt nicht wahr.

  • Die Interaktion mit dem DOM erzeugt null:
  console.log(window.document.getElementById('nonExistentElement')); // null
  • Wie @aleksey-bykov oben erwähnt hat, können Ajax-Operationen null zurückgeben. Tatsächlich ist undefined kein gültiger JSON-Wert:
 JSON.parse(undefined); // error
 JSON.parse(null); // okay
 JSON.stringify({ "foo" : undefined}); // "{}"
 JSON.stringify({ "foo" : null}); // '{"foo":null}'

Hinweis: Wir können so tun, als ob undefined über Ajax zurückgegeben wird, da der Zugriff auf eine nicht vorhandene Eigenschaft zu undefined - weshalb undefined nicht serialisiert wird.

Wenn der Server jedoch in TypeScript geschrieben ist und auf einem Knoten ausgeführt wird, kann er in Gegenwart einer .d.ts-Datei für meinen Front-End-Code transpiliert werden und die Typprüfung garantiert, dass der Server niemals JSON mit Nullen sendet darin oder ein Objekt, dessen x-Eigenschaft keine Zahl ist.

Dies ist nicht ganz richtig. Selbst wenn der Server in TypeScript geschrieben ist, kann man in keiner Weise die Einführung von Nullen garantieren, ohne jede einzelne Eigenschaft jedes einzelnen Objekts zu überprüfen, das aus dem persistenten Speicher erhalten wurde.

Da stimme ich @aleksey-bykov zu. Obwohl es absolut brillant wäre, wenn wir uns von TypeScript zur Kompilierzeit auf Fehler aufmerksam machen könnten, die durch null und undefined eingeführt werden, befürchte ich, dass dies nur ein falsches Vertrauen erweckt und am Ende Trivialitäten aufdeckt, während die wahren Quellen von null unentdeckt bleiben.

Selbst wenn der Server in TypeScript geschrieben ist, kann man in keiner Weise die Einführung von Nullen garantieren, ohne jede einzelne Eigenschaft jedes einzelnen Objekts zu überprüfen, das aus dem persistenten Speicher erhalten wurde.

Dies ist in der Tat ein Argument _für_ Typen, die keine NULL-Werte zulassen. Wenn Ihr Speicher null Foos zurückgeben kann, ist der Typ des aus diesem Speicher abgerufenen Objekts Nullable<Foo>, nicht Foo. Wenn Sie dann eine Funktion haben, die zurückgibt, die Foo zurückgeben soll, müssen Sie die Verantwortung übernehmen, indem Sie die Null behandeln (entweder Sie wandeln sie um, weil Sie es besser wissen, oder Sie prüfen auf Null).

Wenn Sie keine Typen haben, die keine NULL-Werte zulassen, würden Sie nicht unbedingt daran denken, beim Zurückgeben des gespeicherten Objekts auf NULL zu prüfen.

Ich fürchte, es wird nur ein falsches Vertrauensgefühl hervorrufen und am Ende Triviales finden, während die wahren Quellen von Null unentdeckt bleiben.

Welche Art von Nicht-Trivia werden Ihrer Meinung nach nicht auf Null setzbare Typen vermissen?

Dies ist nicht ganz richtig. Selbst wenn der Server in TypeScript geschrieben ist, kann man in keiner Weise die Einführung von Nullen garantieren, ohne jede einzelne Eigenschaft jedes einzelnen Objekts zu überprüfen, das aus dem persistenten Speicher erhalten wurde.

Wenn der persistente Speicher typisierte Daten unterstützt, besteht keine Notwendigkeit. Aber selbst wenn dies nicht der Fall wäre, würden Sie nur die Datenabrufpunkte überprüfen und dann eine Garantie für _allen_ Ihres anderen Codes haben.

Da stimme ich @aleksey-bykov zu. Obwohl es absolut brillant wäre, wenn wir uns von TypeScript zur Kompilierzeit auf Fehler aufmerksam machen könnten, die durch null und undefined eingeführt werden, befürchte ich, dass dies nur ein falsches Vertrauen erweckt und am Ende Trivialitäten aufdeckt, während die wahren Quellen von null unentdeckt bleiben.

Die Verwendung von Nullable-Typen wäre keine absolute Voraussetzung. Wenn Sie der Meinung sind, dass es unnötig ist, die Fälle zu modellieren, in denen eine Methode null zurückgibt, da sie "unbedeutend" sind, können Sie in dieser Typdefinition einfach keinen nullfähigen Typ verwenden (und die gleiche Unsicherheit wie immer erhalten). Es besteht jedoch kein Grund zu der Annahme, dass dieser Ansatz scheitern wird - es gibt Beispiele für Sprachen, die ihn bereits erfolgreich implementiert haben (zB Kotlin von JetBrains )

@aleksey-bykov Ehrlich gesagt haben Sie es völlig falsch verstanden, eines der besten Dinge an Typen, die keine NULL-Werte zulassen, ist _die Möglichkeit, einen Typ als NULL-Werte auszudrücken_.
Mit Ihrer Strategie, null zu verwenden, um Null-Zeiger-Fehler zu vermeiden, verlieren Sie völlig die Möglichkeit, null , aus Angst, Fehler einzuführen, das ist völlig albern.

Eine andere Sache, bitte gehen Sie in einer Diskussion über eine Sprachfunktion nicht mit dummen Kommentaren wie:

Typen ohne Nullwerte sind nutzlos. Typen ohne Nullwerte sind nutzlos. Sie sind nutzlos. Nicht zu gebrauchen! Sie wissen es nicht, aber Sie brauchen sie nicht wirklich.

Das gibt mir nur das Gefühl, dass ich alles ignorieren sollte, was Sie jemals im Web posten werden. Wir sind hier, um über ein Feature zu diskutieren. Ich kann verstehen und akzeptieren, dass Ihre Meinung nicht meine ist, aber benehmen Sie sich nicht wie ein Kind.

Ich bin überhaupt nicht gegen die Einführung von Nicht-Null-Annotationen. Es hat sich in C# und anderen Sprachen als nützlich erwiesen.

Das OP hat den Diskussionsverlauf wie folgt geändert:

Ehrlich gesagt, wenn das Hinzufügen von use not-null der Preis für die Vermeidung aller Null-Zeiger-Ausnahmen ist, würde ich es ohne Probleme bezahlen, da null oder undefiniert als einem Typ zuweisbar zu betrachten ist, ist meiner Meinung nach der schlimmste Fehler, den das Typoskript gemacht hat.

Ich habe lediglich auf die Prävalenz von null und undefined in freier Wildbahn hingewiesen.

Ich sollte auch hinzufügen, dass eines der Dinge, die ich an TypeScript wirklich schätze, die laissez-faire-Einstellung der Sprache ist. Es war ein Hauch frischer Luft.

Darauf zu bestehen, dass Typen standardmäßig nicht auf Null gesetzt werden können, widerspricht diesem Geist.

Ich habe eine Reihe von Argumenten gesehen, warum wir dies tun / nicht brauchen, und ich möchte sehen, ob ich alle zugrunde liegenden Fälle verstehe, die bisher diskutiert wurden:

1) wollen wissen, ob eine Funktion null zurückgeben kann (verursacht durch ihr Ausführungsmuster, nicht durch die Eingabe).
2) wollen wissen, ob ein Wert null sein kann.
3) wollen wissen, ob ein Datenobjekt Nullwerte enthalten kann.

Nun gibt es nur zwei Fälle, in denen Situation 2 eintreten kann: wenn Sie Nullen verwenden oder wenn eine Funktion eine Null zurückgibt. Wenn Sie alle Nullen aus Ihrem Code entfernen (vorausgesetzt, Sie möchten sie nicht), kann Situation 2 wirklich nur als Ergebnis von Situation 1 auftreten.

Situation 1 wird meiner Meinung nach am besten gelöst, indem der Rückgabetyp der Funktion mit Anmerkungen versehen wird, um das Vorhandensein eines Nullwerts anzuzeigen. _Dies bedeutet nicht, dass Sie einen Nicht-Null-Typ benötigen_. Sie können die Funktion mit Anmerkungen versehen (z. B. durch Verwendung von Unionstypen) und keine Nicht-Null-Typen verwenden, es ist wie in der Dokumentation, aber in diesem Fall wahrscheinlich klarer.

Damit ist auch Lösung 2 gelöst.

Dies ermöglicht es Programmierern, die in ihrem Unternehmen arbeiten, Prozesse und Standards zu verwenden, um zu erzwingen, dass Nulltypen markiert werden, und nicht das Typescript-Team (genauso wie das gesamte Typisierungssystem ein Opt-In ist, wären die expliziten Nullable-Typen ein Opt-In.) in).

Was Szenario 3 angeht, ist der Vertrag zwischen dem Server und dem Client nicht für Typescript zu erzwingen. Die Möglichkeit, die betroffenen Werte als möglicherweise null zu markieren, könnte eine Verbesserung sein, aber letztendlich erhalten Sie davon denselben Garentee wie für Typescript Tooling gibt Ihnen für jeden anderen Wert (dh keinen, es sei denn, Sie haben gute Codierungsstandards oder -praktiken!

(Post vom Telefon, sorry für Fehler)

@fdecampredon , es ist in erster Linie nicht die Angst, sondern die Verwendung von null ist unnötig. Ich brauche sie nicht. Als netten Bonus habe ich ein Problem mit Null-Referenz-Ausnahmen beseitigt. Wie ist das alles möglich? Durch die Verwendung eines Summentyps mit einem leeren Fall. Summentypen sind eine native Funktion aller FP-Sprachen wie F#, Scala, Haskell und zusammen mit Produkttypen, die als algebraische Datentypen bezeichnet werden. Standardbeispiele für Summentypen mit leerer Groß-/Kleinschreibung wären Optional von F# und Vielleicht von Haskell. TypeScript hat keine ADTs, aber stattdessen läuft eine Diskussion über das Hinzufügen von Nicht-Nullwerten, die einen Sonderfall dessen modellieren würden, was ADTs abgedeckt hätten. Meine Botschaft ist also, die Nicht-Null-Werte für ADTs wegzulassen.

@spion , Schlechte Nachrichten: F# hat Nullen (als Erbe von .NET). Die gute Nachricht: Niemand verwendet sie. Wie kann man null nicht verwenden, wenn es da ist? Sie haben optionale Optionen (genau wie das neueste Java, wie Sie erwähnt haben). Sie brauchen also nicht null, wenn Ihnen eine bessere Wahl zur Verfügung steht. Dies ist, was ich letztendlich vorschlage: Lassen Sie Nullen (Nicht-Nullen) in Ruhe, vergessen Sie einfach, dass sie existieren, und implementieren Sie ADTs als Sprachfeature.

Wir verwenden null, aber nicht so, wie Sie es verwenden. Mein Firmenquellcode besteht aus 2 Teilen.

1) Bei Daten (wie Datenbankdaten) ersetzen wir zum Zeitpunkt der Variablendeklaration null durch leere Daten.

2) Alle anderen, wie programmierbare Objekte, verwenden wir null, damit wir wissen, wenn es einen Fehler und eine Lücke im Quellcode gibt, in denen keine Objekte erstellt oder zugewiesen werden können, die keine erforderlichen Javascript-Objekte sind. Das undefinierte ist Javascript-Objektprobleme, bei denen ein Fehler oder eine Lücke im Quellcode vorhanden ist.

Die Daten, die wir nicht auf Null setzen möchten, da es sich um Kundendaten handelt und sie Null-Wortlaute sehen.

@aleksey-bykov typescript hat adt mit union type, das einzige, was fehlt, ist der Mustervergleich, aber das ist eine Funktion, die einfach nicht mit der Typescript-Philosophie implementiert werden kann, Javascript in der Nähe der Originalquelle zu generieren.

Auf der anderen Seite ist es unmöglich, mit dem Union-Typ den Ausschluss von Nullwerten zu definieren, deshalb brauchen wir einen Nicht-Null-Typ.

@fdecampredon , TS hat keine ADTs, es hat Unions, die keine

Pattern Matching für ADTs kann so implementiert werden, dass es eng mit generiertem JavaScript übereinstimmt, trotzdem hoffe ich, dass dieses Argument kein Wendepunkt ist

@aleksey-bykov das ist nicht F#. Es ist eine Sprache, die darauf abzielt, die Arten von JavaScript zu modellieren. JavaScript-Bibliotheken verwenden null und undefinierte Werte. Daher sollten diese Werte mit den entsprechenden Typen modelliert werden. Da nicht einmal ES6 algebraische Datentypen unterstützt, ist es angesichts der Designziele von TypeScript nicht sinnvoll, diese Lösung zu verwenden

Außerdem verwenden JavaScript-Programmierer normalerweise if-Prüfungen (in Verbindung mit typeof- und Gleichheitstests) anstelle von Mustervergleichen. Diese können TypeScript-Union-Typen bereits eingrenzen. Von diesem Punkt an ist es nur noch ein winziger Schritt, um nicht-nullierbare Typen mit Vorteilen zu unterstützen, die mit algebraischem Vielleicht usw. vergleichbar sind.

Ich bin überrascht, dass niemand die großen Änderungen erwähnt hat, die lib.d.ts möglicherweise einführen muss, und potenzielle Probleme des vorübergehenden Nullzustands von Klassenfeldern während des Konstruktoraufrufs. Das sind einige echte, tatsächliche potenzielle Probleme bei der Implementierung von Nicht-Nullable-Typen ...

@Griffork Die Idee ist,

declare function getName(personId:number):string|null;

Die Idee ist, dass Sie nur einmal überprüfen, ob der Name null ist, und den gesamten Rest des Codes ausführen, ohne sich Sorgen machen zu müssen, dass Sie Nullprüfungen hinzufügen müssen.

function doSomethingWithPersonsName(personId:number) {
  var name = getName(personId);
  if (name != null) return doThingsWith(name); // type guard narrows string|null to just string
  else { return handleNullCase(); }
}

Und jetzt bist du großartig! Das Typsystem garantiert, dass doThingsWith mit einem Namen aufgerufen wird, der nicht null ist

function doThingsWith(name:string) {
  // Lets create some funny versions of the name
  return [uppercasedName(name), fullyLowercased(name), funnyCased(name)]
}

Keine dieser Funktionen muss auf Null prüfen, und der Code funktioniert auch ohne Auslösen. Und sobald Sie versuchen, eine Nullable-Zeichenfolge an eine dieser Funktionen zu übergeben, wird Ihnen das Typsystem sofort mitteilen, dass Sie einen Fehler gemacht haben:

function justUppercased(personId:number) {
  var name = getName(personId);
  return uppercasedName(name); // error, passing nullable to a function that doesn't check for nulls.
}

Dies ist ein großer Vorteil: Jetzt verfolgt das Typsystem, ob Funktionen nullbare Werte verarbeiten können oder nicht, und außerdem verfolgt es, ob dies tatsächlich erforderlich ist oder nicht. Viel sauberer Code, weniger Kontrollen, mehr Sicherheit. Und hier geht es nicht nur um Strings - mit einer Bibliothek wie runtime-type-checks könnte man auch Typwächter für viel komplexere Daten bauen

Und wenn Ihnen das Tracking nicht gefällt, weil Sie der Meinung sind, dass es sich nicht lohnt, die Möglichkeit eines Nullwerts zu modellieren, können Sie zum guten alten unsicheren Verhalten zurückkehren:

declare function getName(personId:number):string;

und in diesen Fällen warnt Sie die Maschinenschrift nur, wenn Sie etwas offensichtlich falsch machen

uppercasedName(null);

Ich sehe ehrlich gesagt keine Nachteile, außer der Abwärtskompatibilität.

@fdecampredon Gewerkschaftstypen sind genau das, Gewerkschaften. Sie sind keine _disjunkten_ Gewerkschaften, auch bekannt als Summen. Siehe Nr. 186.

@aleksey-bykov

Beachten Sie, dass das Hinzufügen eines Optionstyps immer noch eine bahnbrechende Änderung darstellt.

// lib.d.ts
interface Document {
    getElementById(id: string): Maybe<Element>;
}

...

// Code that worked with 1.3
var myCanvas = <HTMLCanvasElement>document.getElementById("myCanvas");
// ... now throws the error that Maybe<Element> can't be cast to an <HTMLCanvasElement>

Immerhin können Sie jetzt mit Destrukturierung die Optionstypen eines armen Mannes erhalten

class Option<T> {
    hasValue: boolean;
    value: T;
}

var { hasValue, myCanvas: value } = <Option<HTMLCanvasElement>> $("myCanvas");
if (!hasValue) {
    throw new Error("Canvas not found");
}
// Use myCanvas here

aber der einzige Wert daraus ist, wenn lib.d.ts (und alle anderen .d.ts und Ihre gesamte Codebasis, aber wir gehen davon aus, dass wir das beheben können) auch sie verwenden, andernfalls wissen Sie nicht mehr, ob a Eine Funktion, die Option nicht verwendet, kann null zurückgeben oder nicht, es sei denn, Sie sehen sich ihren Code an.

Beachten Sie, dass ich auch nicht dafür bin, dass Typen standardmäßig nicht null sind (jedenfalls nicht für TS 1.x). Es ist eine zu große Veränderung.

Aber sagen wir, wir reden über 2.0. Wenn wir sowieso eine Breaking Change haben (Hinzufügen von Optionstypen), warum nicht auch Typen standardmäßig nicht-nullierbar machen? Typen standardmäßig nicht-nullierbar zu machen und Optionstypen hinzuzufügen ist nicht ausschließlich. Letzteres kann eigenständig sein (z. B. in F#, wie Sie darauf hinweisen), aber ersteres erfordert letzteres.

@Arnavion , es gibt ein Missverständnis, ich habe nicht gesagt, dass wir die Signaturen ersetzen müssen, alle vorhandenen Signaturen bleiben intakt, es steht Ihnen für die neuen Entwicklungen frei, entweder mit ADT oder was auch immer Sie möchten. Also keine Breaking Changes. Nichts wird standardmäßig ungleich null gemacht.

Wenn ATDs vorhanden sind, liegt es an einem Entwickler, alle Stellen zu umschließen, an denen Nullen in die Anwendung eindringen können, indem er sie in optionale Werte umwandelt. Dies kann eine Idee für ein eigenständiges Projekt sein.

@aleksey-bykov

Ich habe nicht gesagt, dass wir die Signaturen ersetzen müssen, alle vorhandenen Signaturen bleiben intakt

_I_ sagte, dass die Signaturen ersetzt werden müssen, und ich habe den Grund bereits angegeben:

aber der einzige Wert daraus ist, wenn lib.d.ts (und alle anderen .d.ts und Ihre gesamte Codebasis, aber wir gehen davon aus, dass wir das beheben können) auch sie verwenden, andernfalls wissen Sie nicht mehr, ob a Eine Funktion, die Option nicht verwendet, kann null zurückgeben oder nicht, es sei denn, Sie sehen sich ihren Code an.


Nichts wird standardmäßig ungleich null gemacht. Immer.

Für TS 1.x stimme ich zu, nur weil es eine zu große Veränderung ist. Für 2.0 wäre die Verwendung des Optionstyps in den Standardsignaturen (lib.d.ts usw.) bereits eine bahnbrechende Änderung. Typen standardmäßig nicht-nullierbar zu machen _zusätzlich_ lohnt sich und bringt keine Nachteile mit sich.

Ich stimme nicht zu, die Einführung von Optionals sollte nichts kaputt machen, es ist nicht so, dass wir entweder Optionals oder Nullables oder Nicht-Nullables verwenden. Jeder nutzt was er will. Alte Vorgehensweisen sollten nicht von neuen Funktionen abhängen. Es liegt am Entwickler, ein geeignetes Werkzeug für seine unmittelbaren Bedürfnisse zu verwenden.

Sie sagen also, dass, wenn ich eine Funktion foo habe, die Option<Nummer> zurückgibt, und eine Funktionsleiste, die eine Zahl zurückgibt, ich nicht sicher sein darf, dass bar nicht null zurückgeben kann, es sei denn, ich schaue mir die Implementierung von bar an oder pflege die Dokumentation "Diese Funktion gibt nie null zurück."? Glauben Sie nicht, dass dies Funktionen bestraft, die nie null zurückgeben?

Die Funktion bar aus Ihrem Beispiel war von Anfang an als nullable bekannt, sie wurde in etwa 100500 Anwendungen überall verwendet und jeder behandelte das Ergebnis als nullable, jetzt kamen Sie herum und schauten hinein und entdeckten, dass null unmöglich ist, bedeutet dies, dass Sie die Signatur von nullable in non-nullable ändern sollten? Ich denke, Sie sollten nicht. Denn dieses Wissen ist zwar wertvoll, aber es lohnt sich nicht, 100.500 Anwendungen abzubremsen. Was Sie tun sollten, ist eine neue Bibliothek mit überarbeiteten Signaturen zu entwickeln, die dies wie folgt macht:

old_lib.d.ts

...
declare function bar(): number; // looks like can return a potentially nullable number
...

überarbeitet_lib.d.ts

declare function bar(): !number; // now thank to the knowledge we are 100% certain it cannot return null

jetzt verwenden die alten Apps weiterhin das old_lib.d.ts , für neue Apps kann ein Entwickler revised_libs.d.ts frei wählen

Leider hat revision_libs.d.ts 50 andere Funktionen, die ich mir noch nicht angesehen habe, die alle eine Zahl zurückgeben (aber ich weiß nicht, ob es wirklich eine Zahl oder eine Nullable-Zahl ist). Was jetzt?

Nehmen Sie sich Zeit, bitten Sie um Hilfe, verwenden Sie die Versionierung (je nach Kenntnisstand, den Sie bisher erworben haben, möchten Sie es möglicherweise stufenweise mit einer ständig steigenden Versionsnummer veröffentlichen: revised_lib.v-0.1.12.d.ts )

Es ist eigentlich nicht nötig. Eine Funktion, die einen Nullable-Typ in der Signatur zurückgibt, aber einen Nicht-Nullable-Typ in der Implementierung, führt nur zu einer redundanten Fehlerprüfung durch den Aufrufer. Die Sicherheit wird dadurch nicht beeinträchtigt. Nach und nach immer mehr Funktionen mit Anmerkungen versehen mit ! wie Sie feststellen, werden sie gut funktionieren, wie Sie sagten.

Ich bin kein Fan von ! nur, weil das Tippen mehr Gepäck erfordert (sowohl in Bezug auf die Tastenanschläge als auch auf die Notwendigkeit, daran zu denken). Wenn wir in 1.x eine Nicht-Nullbarkeit wünschen, dann ! ist eine der oben bereits besprochenen Optionen, aber ich würde trotzdem sagen, dass es sich lohnt, mit 2.0 einen eventuellen Breaking Change zu haben und die Nicht-Nullbarkeit als Standard festzulegen.

Auf der anderen Seite führt dies vielleicht zu einer Python 2/3-ähnlichen Situation, in der jahrelang niemand auf TS 2.0 aktualisiert, weil er es sich nicht leisten kann, seine Millionen-Zeilen-Codebasis durchzugehen und sicherzustellen, dass jede Variablendeklaration und Klasse Member- und Funktionsparameter und... ist mit ? wenn es null sein kann. Selbst 2to3 (das Python 2 to 3-Migrationstool) muss sich nicht mit solchen weitreichenden Änderungen auseinandersetzen.

Ob 2.0 sich leisten kann, eine bahnbrechende Veränderung zu sein, hängt vom TS-Team ab. Ich würde für ja stimmen, aber dann habe ich keine Millionen-Zeilen-Codebasis, die dafür repariert werden muss, also zählt meine Stimme vielleicht nicht.

Vielleicht sollten wir die Funscript-Leute fragen, wie sie die DOM-API, die Nullen zurückgibt, mit F# in Einklang bringen (Funscript verwendet lib.d.ts von TypeScript und andere .d.ts für die Verwendung aus F#-Code). Ich habe es noch nie verwendet, aber wenn ich mir beispielsweise http://funscript.info/samples/canvas/index.html ansehe , scheint der Typanbieter nicht zu glauben, dass document.getElementsByTagName("canvas")[0] jemals sein kann nicht definiert.

Bearbeiten: Hier scheint es, dass document.getElementById() nicht erwartet wird, null zurückzugeben. Zumindest scheint es Option<Element> nicht zurückzugeben, da es auf .onlick auf das Ergebnis zugreift.

@spion
Danke, daran hatte ich nicht gedacht.

Bei der Arbeit ist unsere Codebasis nicht klein, und diese bahnbrechende Änderung, die die Leute wollen, würde uns viel Zeit mit sehr geringem Gewinn zurückwerfen. Durch gute Standards und klare Kommunikation zwischen unseren Entwicklern hatten wir keine Probleme damit, dass Nullen dort auftauchen, wo sie nicht sollten.
Ich bin ehrlich überrascht, dass einige Leute so stark darauf drängen.
Unnötig zu erwähnen, dass uns dies nur sehr wenig Nutzen bringt und uns viel Zeit kosten würde.

@Griffork Sehen Sie sich diese gefilterte Liste von Problemen im Typoskript-Compiler an, um abzuschätzen, wie groß der Nutzen dieser Änderung sein könnte. Alle unter diesem Link aufgeführten "Absturz"-Bugs könnten vermieden werden, indem Typen verwendet werden, die keine NULL-Werte zulassen. Und wir sprechen hier über das großartige Microsoft-Niveau an Standards, Kommunikation und Code-Review.

Was den Bruch angeht, denke ich, dass, wenn Sie weiterhin vorhandene Typdefinitionen verwenden, es möglich ist, dass Sie überhaupt keine Fehler erhalten, außer dass der Compiler auf potenziell nicht initialisierte Variablen und auslaufende Felder hinweist. Auf der anderen Seite können viele Fehler auftreten, insbesondere wenn Klassenfelder in Konstruktoren in Ihrem Code oft nicht initialisiert werden (die später initialisiert werden). Daher verstehe ich Ihre Bedenken und dränge nicht auf eine abwärtsinkompatible Änderung für TS 1.x. Ich hoffe immer noch, dass es mir gelungen ist, Sie davon zu überzeugen, dass, wenn eine Änderung der Sprache es wert ist, die Abwärtskompatibilität zu unterbrechen, diese hier ist.

Auf jeden Fall hat Facebooks Flow Typen, die nicht auf Null gesetzt werden können. Sobald es ausgereifter ist, könnte es sich lohnen, es als Ersatz für TS für diejenigen von uns zu untersuchen, die sich für dieses Problem interessieren.

@spion , die einzige Zahl, die uns Ihre Liste gibt, ist, wie oft null oder undefined aus irgendeinem Grund erwähnt wurde. Im Grunde heißt es nur, dass über null und undefined gesprochen wurde. Ich hoffe, es ist klar, dass Sie es nicht als Argument verwenden können

@aleksey-bykov Das bin ich nicht - ich habe mir _alle_ Probleme in dieser gefilterten Liste angesehen und jeder einzelne, der das Wort "Absturz" enthielt, war mit einem Stack-Trace verbunden, der zeigt, dass eine Funktion versucht hat, auf eine Eigenschaft oder eine Methode zuzugreifen eines undefinierten oder Nullwerts.

Ich habe versucht, den Filter mit verschiedenen Schlüsselwörtern einzugrenzen, und ich (glaube ich) habe es geschafft, alle zu bekommen

@spion
Frage: Wie viele dieser Fehler werden jedoch an Stellen verursacht, an denen sie die Variable als nullable oder undefinierbar markieren müssten?

ZB Wenn ein Objekt ein übergeordnetes Objekt haben kann und Sie parent mit null initialisieren und Sie immer ein Objekt mit einem übergeordneten Element haben, das null ist, müssen Sie das übergeordnete Element dennoch als möglicherweise null deklarieren.
Das Problem hier ist, wenn ein Programmierer Code mit der Annahme schreibt, dass eine Schleife immer unterbrochen wird, bevor sie den Null-Elternteil erreicht. Das ist kein Problem, wenn null in der Sprache vorhanden ist, genau das gleiche würde mit undefined passieren .

Gründe dafür, null so einfach zu verwenden, wie es jetzt ist:
• Dies ist ein besserer Standardwert als undefiniert.
. 1) es ist in einigen Fällen schneller zu überprüfen (unser Code muss sehr performant sein)
. 2) es sorgt dafür, dass In-Schleifen bei Objekten vorhersehbarer arbeiten.
. 3) Es macht die Array-Nutzung sinnvoller, wenn Nullen für leere Werte verwendet werden (im Gegensatz zu fehlenden Werten). Beachten Sie, dass sich delete array[i] und array[i] = undefined bei der Verwendung von indexOf (und wahrscheinlich anderen gängigen Array-Methoden) unterschiedlich verhalten.

Was meiner Meinung nach das Ergebnis der Erstellung von Nullen erfordert, erfordert eine zusätzliche Markierung für die Verwendung in der Sprache:
• Ich habe einen undefinierten Fehler anstelle eines Null-Fehlers erhalten (was in den meisten Typoskript-Szenarien passieren würde).

Als ich sagte, dass wir kein Problem mit dem Escapen von Nullen haben, meinte ich, dass Variablen, die nicht auf null initialisiert sind, niemals null werden ). Die Verwendung von null schwieriger zu machen (indem zusätzliche Syntax erforderlich ist) und das gleiche undefiniert zu lassen, wird einigen Entwicklern (zB uns) tatsächlich mehr Probleme bereiten.

Das Hinzufügen zusätzlicher Syntax zur Verwendung von Null bedeutet, dass Entwickler, die viel Null verwenden, mehrere Wochen / Monate lang Fehler links, rechts und in der Mitte machen, während sie versuchen, sich an die neue Syntax zu erinnern. Und es wird eine weitere Möglichkeit sein, in Zukunft einen Fehler zu machen (indem Sie etwas falsch annotieren). [Es ist an der Zeit, darauf hinzuweisen, dass ich die Idee hasse, Symbole zur Darstellung von Typen zu verwenden, da dies die Sprache weniger klar macht]

Bis Sie mir eine Situation erklären können, in der null einen Fehler oder ein Problem verursacht, das undefined nicht machen würde, stimme ich nicht zu, null wesentlich schwieriger zu verwenden als undefined. Es hat seinen Anwendungsfall, und nur weil es Ihnen nicht hilft, bedeutet das nicht, dass die gewünschte Änderung (die den Arbeitsablauf anderer Entwickler _verletzen_ wird) durchgeführt werden sollte.

Fazit:
Es macht keinen Sinn, Typen ohne NULL-Werte deklarieren zu können, ohne nicht-undefinierte Typen definieren zu können. Und nicht-undefinierte Typen sind aufgrund der Funktionsweise von Javascript nicht möglich.

@Griffork Wenn ich non-nullable sage, meine ich non-nullable-or-undefined. Und es stimmt nicht, dass dies aufgrund der Funktionsweise von JS nicht möglich ist. Mit den neuen Typwächterfunktionen wissen Sie, dass der Wert, der von dort fließt, nicht mehr null oder undefiniert sein kann, sobald Sie einen Typwächter verwenden. Auch hier gibt es einen Kompromiss, und ich reiche die Implementierung von Facebook Flow als Beweis dafür ein, dass es durchaus machbar ist.

Der _nur_ Fall, der etwas schwieriger zu verwenden wird, ist dieser: Ich werde dieser Variablen vorübergehend null zuweisen und dann in dieser anderen Methode verwenden, als ob sie nicht null wäre, aber ich weiß, dass ich diese andere Methode nie zuvor aufrufen werde Initialisieren Sie zuerst die Variable, sodass keine Überprüfung auf Nullen erforderlich ist. Dies ist sehr spröder Code, und ich würde es auf jeden Fall begrüßen, wenn der Compiler mich davor warnt: Es ist sowieso ein Refactor, der kein Fehler ist.

@spion
Ich glaube, dass ich endlich verstehe, woher du kommst.

Ich glaube, Sie möchten, dass Typwächter feststellen, wann ein Wert nicht null sein kann, und Ihnen ermöglichen, Funktionen aufzurufen, die nicht auf null prüfen. Und wenn die schützende if-Anweisung entfernt wird, wird dies zu einem Fehler.

Ich kann sehen, dass das nützlich ist.

Ich glaube auch, dass dies nicht wirklich die Wunderwaffe sein wird, auf die Sie hoffen.

Ein Compiler, der Ihren Code nicht ausführt und nicht jede Facette davon testet, kann nicht besser feststellen, wo Undefinierte/Nullwerte sind, als der Programmierer, der den Code geschrieben hat. Ich befürchte, dass diese Änderung die Leute in ein falsches Sicherheitsgefühl wiegen und es tatsächlich schwieriger machen würde, null/undefinierte Fehler zu verfolgen, wenn sie auftreten.
Wirklich, ich denke, die Lösung, die Sie brauchen, ist ein guter Satz von Testwerkzeugen, die Typescript unterstützen, die diese Art von Fehlern in Javascript reproduzieren können, anstatt einen Typ in einem Compiler zu implementieren, der nicht halten kann, was er verspricht.

Sie erwähnen Flow als Lösung für dieses Problem, aber als ich Ihren Link gelesen habe, habe ich einige Dinge gesehen:

"Flow kann manchmal auch Typen von Objekteigenschaften anpassen, insbesondere wenn es keine Zwischenoperationen zwischen einer Überprüfung und einer Verwendung gibt. Im Allgemeinen schränkt Aliasing von Objekten jedoch den Umfang dieser Argumentation ein , da eine Überprüfung einer Objekteigenschaft möglicherweise durch einen Schreibvorgang in diese Eigenschaft über einen Alias ​​ungültig gemacht werden, und es ist für eine statische Analyse schwierig, Aliase genau zu verfolgen ."

"Undefinierte Werte, genau wie null, können ebenfalls Probleme verursachen. Leider sind undefinierte Werte in JavaScript allgegenwärtig und es ist schwer, sie zu vermeiden, ohne die Benutzerfreundlichkeit der Sprache ernsthaft zu beeinträchtigen[...] Flow macht in diesem Fall einen Kompromiss: it erkennt undefinierte lokale Variablen und Rückgabewerte, ignoriert jedoch die Möglichkeit von Undefinierten, die aus Objekteigenschaften- und Arrayelementzugriffen resultieren ."

Jetzt funktionieren undefiniert und null anders, undefinierte Fehler können immer noch überall auftauchen, das Fragezeichen kann nicht garantieren, dass der Wert nicht null ist, und die Sprache verhält sich anders als Javascript (was TS versucht zu vermeiden von dem, was ich gesehen habe ).

ps

foo(thing: whatiknowaboutmyobject) {
    if (thing.hidden) {
        delete thing.description;
    }
}

if (typeof thing.description === "string") {
    //thing.description is non-nullable now, right?
    foo(thing);
    //What is thing.description?
    console.log(thing.description.length);
}

TS ist bereits anfällig für Aliasing-Effekte (wie jede Sprache, die veränderliche Werte zulässt). Dies wird ohne Fehler kompiliert:

function foo(obj: { bar: string|number }) {
    obj.bar = 5;
}

var baz: { bar: string } = { bar: "5" };

foo(baz);

console.log(baz.bar.charAt(0)); // Runtime error - Number doesn't have a charAt method

Die Flow-Dokumente geben dies nur der Vollständigkeit halber an.

Edit: Besseres Beispiel.

Alt:

Ja, aber ich behaupte, dass die Mittel, etwas auf undefiniert zu setzen, viel größer sind als die Mittel, etwas auf einen anderen Wert zu setzen.

Ich meine,

mything.mystring = 5; // is clearly wrong.
delete mything.mystring; //is not clearly wrong - this is not quite the equivalent of setting mystring to >undefined.

Bearbeiten:
Meh, im Moment ist es so ziemlich persönliche Präferenz. Nachdem ich seit Ewigkeiten Javascript benutzt habe, glaube ich nicht, dass dieser Vorschlag der Sprache helfen wird. Ich denke, es wird die Leute in ein falsches Gefühl der Sicherheit wiegen, und ich denke, dass es Typescript (als Sprache) von Javascript abbringen wird.

@Griffork Um ein Beispiel dafür zu erhalten, wie das aktuelle Typoskript Sie in ein falsches Sicherheitsgefühl einlullt, versuchen Sie das Beispiel, das Sie auf dem Spielplatz präsentiert haben:

var mything = {mystring: "5"}; 
delete mything.mystring;
console.log(mything.mystring.charAt(1));

Übrigens könnte der delete-Operator genauso behandelt werden wie die Zuweisung eines Werts vom Typ null und das würde auch ausreichen, um Ihren Fall abzudecken.

Die Behauptung, dass sich die Sprache anders verhält als JavaScript, ist wahr, aber bedeutungslos. TypeScript hat bereits ein anderes Verhalten als JavaScript. Der Sinn eines Typsystems war schon immer, Programme zu verbieten, die keinen Sinn ergeben. Das Modellieren von Typen, die keine NULL-Werte zulassen, fügt lediglich einige zusätzliche Einschränkungen hinzu. Das Verbieten der Zuweisung von Nullwerten oder undefinierten Werten an eine Variable vom Typ, die keine Nullwerte zulässt, ist genau das gleiche wie das Verbieten einer Zahl an eine Variable vom Typ string. JS erlaubt beides, TS könnte keines zulassen

@spion

Die Idee ist, Nullüberprüfungen überall in Ihrem Code zu vermeiden

Wenn ich verstehe, was Sie befürworten:

A. Machen Sie alle Typen standardmäßig ungleich null.
B. Markieren Sie Felder und Variablen, die null zulässt.
C. Stellen Sie sicher, dass der Anwendungs-/Bibliotheksentwickler alle Einstiegspunkte in die Anwendung überprüft.

Aber bedeutet das nicht, dass die Person, die den Code schreibt, dafür verantwortlich ist, sicherzustellen, dass der Code frei von Nullen ist, und nicht der Compiler? Wir sagen dem Compiler effektiv "dw, ich lasse keine Nullen in das System".

Die Alternative besteht darin zu sagen, Nullen sind überall, also mach dir keine Mühe, aber wenn etwas nicht null ist, lasse ich es dich wissen.

Tatsache ist, dass der letztere Ansatz anfällig für Nullreferenzausnahmen ist, aber wahrheitsgetreuer ist. Vorzugeben, dass ein Feld auf einem Objekt, das über den Draht (dh Ajax) erhalten wurde, nicht null ist, impliziert, dass man an Gott glaubt :smiley: .

Ich glaube, dass es zu diesem Thema starke Meinungsverschiedenheiten gibt, da der obige Punkt C , je nachdem, woran man gerade arbeitet, entweder trivial oder undurchführbar sein könnte.

@jbondc Ich bin froh, dass du das gefragt hast. Tatsächlich ist CallExpression als undefiniert oder nullfähig markiert. Das Typsystem nutzt dies jedoch derzeit nicht aus - es erlaubt immer noch alle Operationen auf typeArguments als ob es nicht null oder undefiniert wäre.

Bei Verwendung der neuen Union-Typen in Kombination mit Nicht-Nullable-Typen könnte der Typ jedoch als NodeArray<TypeNode>|null ausgedrückt werden. Dann lässt das Typsystem keine Operationen in diesem Feld zu, es sei denn, es wird eine Nullprüfung angewendet:

if (ce.typeArguments != null) {
  callSomethingOn(ce.typeArguments)
}

// callSomethingOn doesn't need to perform any checks

function callSomethingOn(na:NodeArray<TypeNode>) {
...
}

Mit Hilfe von TS 1.4-Typwächtern wird der Typ des Ausdrucks innerhalb des if-Blocks auf NodeArray<TypeNode> eingeengt, was wiederum alle NodeArray Operationen an diesem Typ erlaubt; Außerdem können alle innerhalb dieser Prüfung aufgerufenen Funktionen ihren Argumenttyp als NodeArray<TypeNode> angeben, ohne jemals weitere Prüfungen durchführen zu müssen.

Aber wenn du versuchst zu schreiben

function someOtherFunction(ce: CallExpression) {
  callSomethingOn(ce.typeArguments)
}

Der Compiler warnt Sie während der Kompilierung davor, und der Fehler wäre einfach nicht aufgetreten.

Also nein, @NoelAbrahams , hier geht es nicht darum, alles mit

Bei externen Werten liegt es natürlich wie immer an Ihnen, deren Typen anzugeben. Sie können immer sagen, dass externe Daten eine Zeichenfolge anstelle einer Zahl enthalten, und der Compiler wird sich nicht beschweren, aber Ihr Programm stürzt ab, wenn Sie versuchen, Zeichenfolgenoperationen mit der Zahl durchzuführen.

Aber ohne Typen ohne NULL-Werte können Sie nicht einmal sagen, dass ein Wert nicht NULL sein kann. Der Nullwert kann jedem einzelnen Typ zugewiesen werden und Sie können keine Einschränkungen vornehmen. Variablen können nicht initialisiert werden und Sie erhalten keine Warnungen, da undefined ein gültiger Wert für jeden Typ ist. Daher kann der Compiler Ihnen nicht helfen, Null- und nicht definierte Fehler zur Kompilierzeit abzufangen.

Ich finde es überraschend, dass es so viele Missverständnisse über Typen gibt, die nicht auf Null gesetzt werden können. Sie sind nur Typen, die nicht uninitialisiert bleiben können oder denen die Werte null oder undefined zugewiesen werden können. Dies ist nicht unähnlich, wenn Sie einer Zahl keine Zeichenfolge zuweisen können. Dies ist mein letzter Beitrag zu diesem Thema, da ich das Gefühl habe, dass ich nicht wirklich weiterkomme. Wenn jemand daran interessiert ist, mehr darüber zu erfahren, empfehle ich, mit dem oben erwähnten Video "Der Milliarden-Dollar-Fehler" zu beginnen. Das Problem ist bekannt und wird von vielen modernen Sprachen und Compilern erfolgreich angegangen.

@spion , ich stimme allen Vorteilen wenn ich _standardmäßig nicht null

Vorzugeben, dass ein Feld auf einem Objekt, das über den Draht (dh Ajax) erhalten wurde, nicht null ist, bedeutet, an Gott zu glauben

Also tu nicht so, als ob. Markieren Sie es als NULL-fähig und Sie werden gezwungen sein, es zu testen, bevor Sie es als nicht NULL-fähig verwenden.

Sicher. Es läuft darauf hinaus, ob wir ein Feld als NULL-fähig markieren möchten (in einem System, in dem Felder standardmäßig nicht NULL sind) oder ob wir ein Feld als NULL-nicht markieren möchten (in einem System, in dem Felder standardmäßig NULL-Werte aufweisen).

Das Argument ist, dass ersteres eine bahnbrechende Änderung ist (die von Bedeutung sein kann oder nicht) und auch unhaltbar ist, da sie vom Anwendungsentwickler verlangt, alle Einstiegspunkte zu überprüfen und zu garantieren.

@NoelAbrahams Ich verstehe nicht, warum es die meiste Zeit 'absichtlich' ist, wenn Sie null wollen, auch wenn ein Einstiegspunkt null zurückgeben kann, müssen Sie es überprüfen Standardmäßig können Sie weniger Nullprüfungen durchführen, da Sie einer API/Bibliothek/einem Einstiegspunkt in Ihrer Anwendung vertrauen können.

Wenn Sie ein wenig darüber nachdenken, einen Typ als Nicht-Null in einem Nullable-Typ-System zu markieren, hat der Wert einen begrenzten Wert, Sie können immer noch einen Nullable-typisierten Variablen-/Rückgabetyp verwenden, ohne ihn testen zu müssen.
Es wird auch den Definitionsautor zwingen, mehr Code zu schreiben, da eine gut gestaltete Bibliothek meistens weder null noch einen undefinierten Wert zurückgibt.
Schließlich ist sogar das Konzept seltsam, in einem Typsystem mit nicht nullbarem Typ ist ein nullabler Typ perfekt als Unionstyp ausdrückbar: ?string ist das Äquivalent von string | null | undefined . Wie würden Sie !string in einem Typsystem mit Nullable-Typ als Standardwert ausdrücken, in dem Sie den Typ als Nicht-Nullable-Typ markieren können? string - null - undefined ?

Letztendlich verstehe ich die Besorgnis der Leute hier nicht wirklich, null ist kein String, genauso wie 5 kein String ist, beide Werte werden nicht in der Lage sein verwendet werden, wo eine Zeichenfolge erwartet wird, und das Ausrutschen var myString: string = null ist genauso fehleranfällig wie: var myString: string = 5 .
Null oder undefiniert jedem Typ zuweisbar zu haben ist vielleicht ein Konzept, mit dem Entwickler vertraut sind, aber es ist immer noch ein schlechtes.

Ich glaube nicht, dass ich mit meinem vorherigen Post ganz richtig lag: Ich gebe es zu jeder vollen Stunde.

Ich habe gerade einen Teil unseres Codes durchgesehen, um zu sehen, wie die Dinge funktionieren würden, und es würde sicherlich helfen, bestimmte Felder als NULL-fähig zu markieren, zum Beispiel:

interface Foo {
        name: string;
        address: string|null; /* Nullable */
}

var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.toString(); // Error: do not use without null check

Was ich aber beanstande ist folgendes:

foo.name = undefined; // Error: non-nullable

Ich habe das Gefühl, dass dies die natürliche Arbeitsweise mit JavaScript beeinträchtigen wird.

Genau das gleiche könnte mit Nummer gelten:

interface Foo {
        name: string;
        address: string|number; 
}
var foo:Foo = new FooClass();
foo.name.toString(); // Okay
foo.address.slice() // error

foo.name  = 5 // error

Und es ist immer noch in JavaScript gültig

Wie oft weisen Sie einer Eigenschaft eines Objekts bereitwillig null zu?

Ich denke, dass die meisten Dinge als null markiert wären, aber Sie würden sich auf Typwächter verlassen, um zu deklarieren, dass das Feld jetzt nicht nullfähig ist.

@fdecampredon
Eigentlich ziemlich viel.

@Griffork ,

Ich denke, dass die meisten Dinge als null markiert würden

Das war mein erster Gedanke. Aber nachdem ich einige Abschnitte unseres Codes durchgegangen war, fand ich Kommentare wie die folgenden:

interface MyType {

     name: string;

     /** The date the entry was updated from Wikipedia or undefined for user-submitted content. */
     wikiDate: Date; /* Nullable */
}

Die Idee, dass ein Feld NULL-Werte zulässt, wird häufig verwendet, um Informationen bereitzustellen. Und TypeScript fängt Fehler ab, wenn es beim Zugriff auf wikiDate einen Typschutz erfordert.

@fdecampredon

foo.name = 5 // error
Und es ist immer noch in JavaScript gültig

Stimmt, aber das ist ein Fehler, da TypeScript mit 100%iger Sicherheit weiß, dass dies nicht beabsichtigt war.

wohingegen

foo.name = undefiniert; // Namen nicht an Server senden

ist vollkommen gewollt.

Ich denke, die Implementierung, die unseren Anforderungen am besten entspricht, besteht darin, keine Unionstypen zu verwenden, sondern sich an den ursprünglichen Vorschlag zu halten:

 wikiDate: ?Date;

Ich stimme @NoelAbrahams zu

foo.name = 5 // Fehler
Und es ist immer noch in JavaScript gültig
Stimmt, aber das ist ein Fehler, da TypeScript mit 100%iger Sicherheit weiß, dass dies nicht beabsichtigt war.

Der Compiler weiß nur , dass Sie Namen wie markiert string und nicht string | number , wenn Sie einen Nullable - Wert wollen würden Sie es einfach markieren als ?string oder string | null (die ist ziemlich gleichwertig)

Ich denke, die Implementierung, die unseren Anforderungen am besten entspricht, besteht darin, keine Unionstypen zu verwenden, sondern sich an den ursprünglichen Vorschlag zu halten:

wikiDate: ?Datum;

Wir sind uns also einig, dass der Typ standardmäßig nicht null ist und Sie nullable mit ? markieren würden :) .
Beachten Sie, dass es sich um einen Unionstyp handelt, da ?Date das Äquivalent von Date | null | undefined :)

Oh, Entschuldigung, ich habe versucht, standardmäßig NULL zuzulassen und nicht mit spezieller Eingabe (die Symbole sind verwirrend).

@fdecampredon , eigentlich bedeutet es, wenn ein Feld oder eine Variable als NULL- Typwächter erforderlich:

var wikiDate: ?Date;

wikiDate.toString(); // error
wikiDate && wikiDate.toString(); // okay

Dies ist keine bahnbrechende Änderung, denn wir sollten dies immer noch können:

 var name: string;   // okay
 name.toString();  // if you think that's fine then by all means

Vielleicht glauben Sie, dass wir dies nicht erreichen können, ohne null in Unionstypen einzuführen?

Ihr erstes Beispiel ist absolut richtig, wenn Sie dies tun:

wikiDate && wikiDate.toString(); // okay

Sie verwenden einen Typeguard und der Compiler sollte nichts warnen.

Dein zweites Beispiel ist jedoch nicht gut

var name: string;   // okay
name.toString();  // if you think that's fine then by all means

der Compiler sollte hier einen Fehler haben, ein einfacher Algorithmus könnte nur in der ersten Zeile einen Fehler machen (unitialisierte Variable nicht als nullable markiert), ein komplexerer könnte versuchen, die Zuweisung vor der ersten Verwendung zu erkennen:

var name: string;   // okay
name.toString();  // error because not initialized
var name: string;
if (something) {
  name = "Hello World";
} else {
  name = "Foo bar";
}
name.toString();  // no error since name will always be initialized.

Ich weiß nicht genau, wo ich die Barriere setzen soll, aber es bedarf sicherlich einer subtilen Feinabstimmung, um den Entwickler nicht zu behindern.

Es ist eine bahnbrechende Änderung und kann nicht vor 2.0 eingeführt werden, außer vielleicht mit der von @metaweta vorgeschlagenen 'use nonnull'-Direktive

Warum nicht:

var string1: string; //this works like typescript does currently, doesn't need type-guarding before use, null and undefined can be assigned to it.
string1.length; //works
var string2: !string; //this doesn't work because the string must be assigned to a non-null and non-undefined value, doesn't need type-guarding before use.
var string3: ?string; //this must be type guarded to non-null, non-undefined before use.
var string4: !string|null = null; //This may be null, but should never be undefined (and must be type-guarded before use).
var string5: !string|undefined; //this may never be null, but can be undefined (and must be type-guarded before use).

Und haben Sie ein Compiler-Flag (das nur funktioniert, wenn -noimplicitany aktiviert ist), das -noinferrednulls sagt, das die normale Syntax für Typen (wie string und int) deaktiviert und Sie ein ? oder ! mit ihnen (null, undefiniert und alle Typen sind Ausnahmen).

Auf diese Weise sind Nicht-Null-Werte ein Opt-In, und Sie können das Compiler-Flag verwenden, um zu erzwingen, dass ein Projekt explizit auf Null gesetzt wird.
Der Compiler weist Fehler bei der Zuweisung von Typen auf , nicht nach (wie beim vorherigen Vorschlag).

Die Gedanken?

Bearbeiten: Ich habe dies geschrieben, weil es die Idee der Verwendung von Nicht-Null bei jeder Aktion erzwingt. Jeder, der den Code liest, der aus einem anderen TS-Projekt stammt, weiß genau, was passiert. Auch das Compiler-Flag wird sehr offensichtlich, wenn es aktiviert ist (da blah: string ein Fehler ist, blah:!string jedoch nicht, ähnlich wie bei -noimplicitany).

Bearbeiten2:
DefinatelyTyped könnte dann aktualisiert werden, um noninferrednulls zu unterstützen, und sie werden die Verwendung der Bibliotheken nicht ändern, wenn Leute sich dafür entscheiden, sich nicht für das ? und ! Merkmal.

Es ist mir egal, ob non-null und non-undefined opt-in, opt-out, with sind
Typmodifizierer (!?), eine Direktive oder ein Compiler-Flag; Ich werde tun, was immer es ist
dauert, um sie zu bekommen _solange sie sich ausdrücken können_, was nicht der Fall ist
derzeit der Fall.

Am Montag, 22. Dezember 2014 um 14:35 Uhr schrieb Griffork [email protected] :

Warum nicht:

var string1: string; //Dies funktioniert wie derzeit Typescript., benötigt keine Typüberwachung vor der Verwendung, null und undefined können zugewiesen werden.
string1.länge; //worksvar string2: !string; //Dies funktioniert nicht, da der String einem Nicht-Null-Wert und einem nicht-undefinierten Wert zugewiesen werden muss, keine Typüberwachung vor der Verwendung erforderlich ist.var string3: ?string; //Dies muss vor use.var string4: !string|null = null; //Dies kann null sein, sollte aber niemals undefiniert sein (und muss vor der Verwendung typgeschützt sein).var string5: !string|undefined; //dies darf nie null sein, kann aber undefiniert sein (und muss vor der Verwendung typgeschützt sein).

Und haben ein Compiler-Flag (das nur funktioniert, wenn -noimplicitany aktiviert ist), das
sagt -noinferrednulls, was die normale Syntax für Typen deaktiviert (wie
string und int) und Sie müssen ein ? oder ! mit ihnen (null, undefiniert
und alle Ausnahmen).

Auf diese Weise sind Nicht-Null-Werte ein Opt-In, und Sie können den Compiler verwenden
Flag, um zu erzwingen, dass ein Projekt explizit auf Null gesetzt wird.
Die Compiler-Flag-Fehler bei der _Zuweisung von Typen_, nicht nach (wie
der vorherige Vorschlag).

Die Gedanken?

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

Mike Stay - [email protected]
http://www.cs.auckland.ac.nz/~mike
http://reperiendi.wordpress.com

@Griffork das wäre eine Option, aber meiner Meinung nach eine schlechte, und ich werde erklären, warum:

  • Es wird viel mehr Arbeit in Definitionsdateien verursachen, da wir jetzt jeden Typ überprüfen und mit Anmerkungen versehen müssen, um die richtige Definition zu haben.
  • Am Ende schreiben wir !string (und manchmal ?string ) überall in den Code, was den Code viel weniger lesbar machen würde.
  • !string in einem System, in dem Typen nullablesbar sind, ist ein seltsames Konzept, die einzige Möglichkeit, es wirklich zu beschreiben, ist string minus null minus undefined , im Gegenteil, ?string ist ziemlich einfach zu beschreiben in ein Typsystem, bei dem type standardmäßig null ist string | null | undefined .
  • Ich sehe eine Menge Kopfschmerzen (und Leistungsverluste) voraus, um einen Typprüfungsalgorithmus zu finden, bei dem der Compiler versteht, dass string | null einen string jedoch nicht. Sie führen im Grunde ein Konzept ein, bei dem einige Gewerkschaftstypen sollten anders behandelt werden als andere.
  • Und schließlich das Schlimmste, wir verlieren die Typinferenz var myString = "hello" was myString sein sollte string , ?string oder !string ? ehrlich gesagt ein großes Kopfzerbrechen hier.

Wenn wir keinen Nicht-Null-Typ als Standard haben, ist der beste Vorschlag, den ich hier gesehen habe, die von @metaweta vorgeschlagene Direktive 'use non-null' .
Sicher, es muss schön angegeben werden, aber zumindest mit nur einem use non-null String in unserer gesamten Datei können wir ein einfaches und vorhersehbares Verhalten erzielen.

@fdecampredon

  1. Es mag in Definitionsdateien viel mehr Arbeit bedeuten, aber _Sie müssten diese Arbeit sowieso erledigen_ (um sicherzustellen, dass die Typen korrekt sind) und diesmal erinnert Sie der Compiler daran, was Sie noch nicht bearbeitet haben (wenn Sie -noimplicitnull . verwenden) ).
  2. Ich bin offen für andere Vorschläge für Anmerkungen. Ich glaube ehrlich, dass das aktuelle Typsystem seinen Platz hat und nicht _ersetzt_ werden sollte. Ich glaube nicht, dass sich ein Breaking Change lohnt. Stattdessen denke ich, dass wir einen besseren Weg finden sollten, um zu beschreiben, was Sie suchen. (Ich mag die Idee, all dies mit Symbolen darzustellen, wirklich, wirklich nicht, sie sind nicht intuitiv.)
  3. Was ist daran schwer zu beschreiben? Ich habe an anderer Stelle im Typoskript Diskussionen gesehen, in denen diese Anfrage (für bestimmte Typen) vorgeschlagen wurde (ohne Markierung). Habe schnell gesucht und konnte das Problem nicht finden, ich werde später mehr suchen.

  4. Wenn Sie sich auf das beziehen, was ich als !string|null , würde das im aktuellen System funktionieren, wenn null _like_ {} behandelt würde (aber ihm nicht zuweisbar wäre).
    Wenn Sie von string|null sprechen, die ich nicht in meiner Liste hatte, dann denke ich, dass null in diesem Fall ignoriert werden sollte. Null und undefiniert sind nur in Unions sinnvoll, in denen vor jedem Nicht-Null und Nicht-Undefinierten ein ! und any ist kein any (dies könnte eine Compiler-Warnung/ein Fehler sein).
  5. Gute Frage, und eine, die nur auftritt, wenn Sie die Option -noimplicitnull verwenden. Ich denke, die sicherste Option wäre, sie der Option zuzuweisen, die am ehesten einen frühen Fehler verursacht (wahrscheinlich nullfähig), aber ich bekomme die Ich habe das Gefühl, dass es eine bessere Idee gibt, an die ich nicht denke. Ich frage mich, ob noch jemand einen Vorschlag hat, wie das angegangen werden sollte?

Edit: Zu Punkt 2 hinzugefügt.
Edit: Tippfehler in Punkt 4 behoben.

In Definitionsdateien kann es viel mehr Arbeit bedeuten, aber Sie müssten diese Arbeit trotzdem tun (um sicherzustellen, dass die Typen korrekt sind) und diesmal erinnert Sie der Compiler daran, was Sie noch nicht bearbeitet haben (wenn Sie -noimplicitnull . verwenden) ).

Ja und nein, überprüfen Sie die von Ihnen verwendete Bibliothek und sehen Sie, wie viel tatsächlich null oder undefiniert zurückgibt.
es ist ein ziemlich seltener Fall, wir konnten in dieser Ausgabe nur sehr wenige Vorkommen für die Standardbibliothek finden und zum Beispiel die Promise-Bibliothek tut dies nie.
Mein Punkt ist, dass in einem Typsystem, in dem type standardmäßig nicht nullablesbar ist, die meisten der vorhandenen Definitionsdateien bereits gültig sind .

Ich bin offen für andere Vorschläge für Anmerkungen. Ich glaube ehrlich, dass das aktuelle Typensystem seinen Platz hat und nicht ersetzt werden sollte. Ich glaube nicht, dass sich ein Breaking Change lohnt. Stattdessen denke ich, dass wir einen besseren Weg finden sollten, um zu beschreiben, was Sie suchen. (Ich mag die Idee, all dies mit Symbolen darzustellen, wirklich, wirklich nicht, sie sind nicht intuitiv.)

Ich glaube nicht, dass es so einen Weg gibt, aber ich hoffe, dass ich falsch liege, denn er hat meiner Meinung nach einen unschätzbaren Wert.
Was den Breaking Change-Teil angeht, warum sind Sie so gegen die 'use non-null' Direktive?
Entwickler, die wünschen, dass ein Nullable Type-System überhaupt nicht betroffen wäre (es sei denn, sie fügen seltsamerweise bereits 'use non-null' am Anfang ihrer Datei hinzu, aber ehrlich gesagt, das wäre ein bisschen ... seltsam)
Und Entwickler, die ein Nicht-Null-Typ-System wünschen, könnten einfach die neue Direktive verwenden.

Was ist daran schwer zu beschreiben? Ich habe an anderer Stelle im Typoskript Diskussionen gesehen, in denen diese Anfrage (für bestimmte Typen) vorgeschlagen wurde (ohne Markierung). Habe schnell gesucht und konnte das Problem nicht finden, ich werde später mehr suchen.
Ich finde das Konzept nur ein bisschen 'komisch' und nicht sehr klar, ich verwende im Allgemeinen Komposition als Hauptwerkzeug für die Programmierung, nicht _Dekomposition_, aber warum nicht.

Wenn Sie sich auf das beziehen, was ich als !string|null geschrieben hatte, würde das im aktuellen System funktionieren, wenn null wie {} behandelt würde (aber ihm nicht zuweisbar wäre). Wenn Sie von string|null sprechen, das ich nicht in meiner Liste hatte, dann denke ich, dass null in diesem Fall ignoriert werden sollte. Null und undefiniert sind nur in Unions sinnvoll, in denen vor jedem Nicht-Null und Nicht-Undefinierten ein ! und any ist kein any (dies könnte eine Compiler-Warnung/ein Fehler sein).
Ich beziehe mich auf die Tatsache, dass bei der Zerlegung der 3 Typen:

  • !sting : string - null - undefined
  • string : string | null | undefined
  • ?string : string | null | undefined

Die 2 neuesten haben im Grunde keinen Unterschied, aber der Compiler sollte wissen, dass er für string keine Typschutzprüfung erzwingen sollte und für ?string sollte, dass Informationen überall verbreitet werden müssen, die Der Algorithmus wird viel komplexer sein, als er tatsächlich ist, und ich bin mir ziemlich sicher, dass ich einen seltsamen Fall mit Typinferenz finden könnte.

Gute Frage, und eine, die nur auftritt, wenn Sie die Option -noimplicitnull verwenden. Ich denke, die sicherste Option wäre, sie der Option zuzuweisen, die am ehesten einen frühen Fehler verursacht (wahrscheinlich nullfähig), aber ich bekomme die Ich habe das Gefühl, dass es eine bessere Idee gibt, an die ich nicht denke. Ich frage mich, ob noch jemand einen Vorschlag hat, wie das angegangen werden sollte?

Würde es nicht tun, würde es im Grunde das gleiche Problem mit sich bringen, das @RyanCavanaugh kommentierte, als ich darüber nachdachte, einfach ein Flag einzuführen, das es ermöglichen würde, von null als Standard auf null als Standard umzuschalten.
In diesem Fall war der frühere Vorschlag viel einfacher.

Nochmals, warum bist du gegen die 'use non-null' Direktive, mehr denke ich darüber nach, es scheint mir die ideale Lösung zu sein.

@fdecampredon
Weil die "Verwendung von Nicht-Null", wie sie derzeit vorgeschlagen wird, die Art und Weise ändert, wie die Sprache _verwendet_ wird, nicht die Art, wie die Sprache _geschrieben_ wird. Das bedeutet, dass eine Funktion von einem Ort, wenn sie an einen anderen Ort verschoben wird, anders funktionieren kann, wenn sie _verwendet_ ist. Sie erhalten Fehler bei der Kompilierung, die möglicherweise 1-3 Dateien entfernt sind, weil etwas falsch eingegeben wurde.

Der Unterschied zwischen:
string
und
?string
Ist das das ? und ! symbol bittet um eine strikte Typprüfung für diese Variable (ähnlich wie Ihre "use nonnull"-Direktive, aber auf Variablenbasis). Erklären Sie es so und ich denke, Sie werden keine großen Probleme damit haben, dass die Leute es verstehen.

Der Unterschied ist natürlich:
Datei 1:

//...187 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null;
    }
    else {
        return "hello";
    }
}

Datei 2:

"use nonnull"
//...2,748 lines of code down...
string myfoo(checker: boolean) {
    if(checker){
        return null; //Error!
    }
    else {
        return "hello";
    }
}

Entwickler müssen jetzt eine mentale Karte darüber führen, welche Dateien nicht null sind und welche nicht. Ich glaube ehrlich gesagt, dass dies eine schlechte Idee ist (auch wenn der größte Teil Ihres Codes 'nicht null verwenden' ist).

Bearbeiten: Auch wenn Sie mit der Eingabe einer Funktion beginnen und das kleine Fenster erhalten, das Ihnen mitteilt, was die Funktionsdefinition ist, woher wissen Sie dann, ob string in dieser Definition null- oder nicht-nullfähig ist?

@Griffork

  • das ist wieder bei 'use strict' der Fall.
  • Zumindest die Idee ist einfach. und einfaches Konzept in das Typsystem einführen.
  • Wenn ein Entwickler eine Funktion in eine andere Datei verschiebt, scheint es für mich ein ziemlicher Randfall zu sein, und da der Fehler, den er erhält, eine Nullprüfung betrifft, kann er schnell verstehen, was passiert ...

Auch hier ist es nicht perfekt, aber ich sehe keine bessere Alternative.

Um es klar zu sagen, Ihre einzigen Probleme mit dem, was ich vorgeschlagen habe (nach Gegenargumenten), die ich sehen kann, sind:

  • Wir wissen nicht, wie sich var mystring = "string" verhalten würde
  • Sie möchten nicht überall Symbole eingeben müssen.

Und meine Bedenken bezüglich der Richtlinie sind:

  • Nicht-Null-Werte wären nicht explizit (können aber im selben Projekt wie NULL-Werte vorkommen). Edit : Wortlaut korrigiert, um mehr Sinn zu machen.
  • Es wird für Entwickler schwieriger sein, den Überblick zu behalten, ob das, was sie schreiben, nullablesbar ist oder nicht.
  • Die Funktionsdefinitionen, die Sie beim Aufrufen einer Funktion sehen ( edit : das von Visual Studio bereitgestellte Popup, wenn Sie mit der Eingabe einer Funktion beginnen) können NULL-fähig sein oder nicht, und Sie können es nicht erkennen.
  • Sobald eine Funktion durch 2 oder 3 Ebenen gewickelt ist, wissen Sie nicht, ob ihre Definition noch richtig ist (da Sie keine Typinferenz durch Dateien verwenden können, die nicht "ungleich NULL verwenden" haben).

Ehrlich gesagt, die Implementierung von "use strict" sollte nicht angestrebt werden . Es wurde für JavaScript (nicht Typescript) entwickelt, und für JavaScript gibt es nur wenige Alternativen. Wir verwenden Typescript, also haben wir die Möglichkeit, die Dinge besser zu machen.
Und ich kann nicht sehen, warum die Direktive besser ist, als Entwickler zu zwingen, ihre Absichten offen zu legen (schließlich ist das der Grund, warum Typescript erstellt wurde, nicht wahr?).

Edit: Ein Punkt geklärt.
Edit: String in Anführungszeichen setzen

Ehrlich gesagt ist Ihre Zusammenfassung meiner Bedenken etwas klein und spiegelt nicht das wider, was ich geschrieben habe erstellen Sie speziell für Typrückschlüsse und machen Sie Code umsonst extrem ausführlich. Im Grunde nicht das richtige Werkzeug für den Job.

Ich möchte, dass alle Nicht-Null-Werte explizit sind (da sie im selben Projekt wie NULL-Werte vorkommen können).

Ich möchte die Tatsache, dass string tatsächlich string | null | undefined ohne dass Sie gezwungen werden, dies explizit zu überprüfen.

Es wird für Entwickler schwieriger sein, den Überblick zu behalten, ob das, was sie schreiben, nullablesbar ist oder nicht.

Ich bezweifle, dass es in einem Projekt eine einzelne Datei ohne "use non-null" geben wird, wenn das Projekt nicht-null verwendet und das Scrollen an der Spitze Ihrer Datei nicht so schwer ist (zumindest wenn Sie eine Datei mit weniger als 500 Zeilen Code schreiben) was in den meisten Fällen der Fall ist...)

Die Funktionsdefinitionen, die Sie beim Aufrufen einer Funktion sehen, können NULL-fähig sein oder nicht, und Sie können es nicht erkennen.

Ja, das wirst du, wenn es von einem Modul kommt, das eine Direktive hat, die 'use non-null' hat, wird entsprechend typisiert, wenn du nicht einfach alles als nullbar betrachtest ...

Sobald eine Funktion durch 2 oder 3 Ebenen gewickelt ist, wissen Sie nicht, ob ihre Definition noch richtig ist (da Sie keine Typinferenz durch Dateien verwenden können, die nicht "ungleich NULL verwenden" haben).

Ich verstehe deinen Punkt hier nicht.

Ehrlich gesagt ist die Implementierung von "use strict" nicht anzustreben. Es wurde für JavaScript (nicht Typescript) entwickelt, und für JavaScript gibt es nur wenige Alternativen. Wir verwenden Typescript, also haben wir die Möglichkeit, die Dinge besser zu machen.
Und ich kann nicht sehen, warum die Direktive besser ist, als Entwickler zu zwingen, ihre Absichten offen zu legen (schließlich ist das der Grund, warum Typescript erstellt wurde, nicht wahr?).

TypeScript ist eine Obermenge von Javascript, es hat seine Wurzel in JavaScript und das Ziel besteht darin, Javascript auf sicherere Weise zu schreiben, so dass die Wiederverwendung eines JavaScript-Konzepts nicht unvernünftig erscheint.

Und ich kann nicht sehen, warum die Richtlinie besser ist, als Entwickler zu zwingen, ihre Absichten offen zu legen

Da Sie einfach nicht in der Lage sein werden, das gleiche Ergebnis zu erzielen, ist es aus all den Gründen, die ich angeführt habe, einen nicht-nullbaren Typ in einem nullable-Typsystem zu haben, nur eine schlechte Idee.

Aus meiner Sicht gibt es hier 3 praktikable Lösungen:

  • Breaking Changes für 2.0, bei denen Typen nicht auf Null gesetzt werden können
  • Compiler-Flag, die standardmäßig zu einem nicht nullbaren Typ wechselt, wie ich ein paar Dutzend Kommentare vorgeschlagen habe, aber
  • 'Nicht-Null-Anweisung verwenden'

Die Flagge wäre es mir übrigens total wert.

function fn(x: string): number;
function fn(x: number|null): string;

function foo() {
    return fn(null);
}

var x = foo(); // x: number or x: string?

Wenn Funktionen das einzige Problem sind, könnte eine Ausnahme in Fällen gemacht werden, in denen eine Überladung ein explizites null -Argument enthält - es würde immer Vorrang vor impliziten Nullen haben (um mit --noImplicitNull harmonieren zu können). . Dies wäre auch die Interpretation, die für den Benutzer sinnvoll wäre (dh "das, was ich explizit sage, sollte das implizit Gesagte überschreiben"). Obwohl ich mich frage, ob es andere ähnliche Probleme mit der Flagge gibt, die auf diese Weise nicht gelöst werden können. Und natürlich fügt dies sowohl der Spezifikation als auch der Implementierung eine gewisse Hack-Komplexität hinzu :|

  1. string|null|undefined war in meinem Vorschlag mit dem Flag explizit, deshalb habe ich es weggelassen.
  2. Warum dann pro Datei? Ich schlage meinen Vorschlag vor, weil er _die Abwärtskompatibilität nicht beeinträchtigt_, was für mich und wahrscheinlich für viele andere Entwickler wichtig ist. Und es kann erzwungen werden, projektweit zu sein.
  3. Ich verwende viele Dateien, die ich nicht erstelle; Woher soll ich wissen, dass diese bestimmte Datei "Use nonnull" hat? Wenn ich 100 Dateien habe, die von anderen Leuten erstellt wurden, muss ich mir merken, welche dieser 100 Dateien ungleich null ist? (oder, _jedes Mal_ wenn ich eine Variable/Funktion aus einer anderen Datei verwende, muss ich diese Datei öffnen und überprüfen?)
  4. Mal sehen, ob ich das klarer machen kann: Beispiel am Ende des Beitrags.
  5. Wo hörst du auf? Wo nennst du es schlecht? Eine Zeichenfolge oder ein Schlüsselwort am Anfang einer Datei sollte das Verhalten einer Datei nicht ändern. "use strikt" wurde zu Javascript hinzugefügt, weil

    1. Zu dieser Zeit gab es keine große Übermenge von Javascript (zB Typescript), die tun konnte, was sie wollte.

    2. Es war ein Versuch, die Verarbeitung von JavaScript zu

      "use strict" wurde übernommen, _nicht weil es richtig war_, sondern weil die Browser nur so auf die Anforderungen der Entwickler eingehen konnten. Ich würde es hassen, wenn Typskript 1 (dann 2, dann 3, dann 4) andere Direktiven hinzufügt, die _die Art und Weise, wie die Sprache funktioniert, grundlegend ändern_ als Zeichenfolgen, die in einem beliebigen Bereich deklariert werden und sich auf andere beliebige Bereiche auswirken. Es ist wirklich schlechtes Sprachdesign. Ich würde mich freuen, wenn "use strict" in Typescript nicht existiert, sondern ein Compiler-Flag wäre (und Typescript es in jede Datei/jeden Bereich ausgibt, die es benötigt).

  6. Javascript ist kein "non-nullable type system", Typescript ist kein "non-nullable type system". Die Einführung der Möglichkeit, Typen ohne NULL-Werte zu deklarieren, bedeutet nicht, dass das gesamte System "nicht-nullierbar" ist. Es ist nicht der Sinn von Typescript.
File 1:
"use notnull"
export string foo() {
    return "mygeneratedstring";
}
File 2:
export string foo() {
    return file1.foo()
}
File 3:
"use notnull"
file2.foo(); //???

Sie haben tatsächlich die Möglichkeit, Kontextinformationen zu


Wir scheinen uns im Kreis zu streiten. Wenn ich für Sie keinen Sinn ergebe, dann tut es mir leid, aber Sie scheinen immer wieder die gleichen Probleme anzusprechen, und ich antworte wiederholt darauf.

@spion
In diesem Beispiel ist die Zeichenfolge keine implizite Null (ich glaube, es wird davon ausgegangen, dass das Flag --noImplicitNull aktiviert ist).

string|null|undefined war in meinem Vorschlag mit dem Flag explizit, deshalb habe ich es weggelassen

Was ich meine ist, dass im tatsächlichen System var myString: string implizit var myString: (string | null | undefined); und der Compiler Sie außerdem nicht dazu zwingt, typeguard zu verwenden, es sei denn, alle anderen Unionstypen.

Warum dann pro Datei? Ich schlage meinen Vorschlag vor, weil er die Abwärtskompatibilität nicht beeinträchtigt, was für mich und wahrscheinlich für viele andere Entwickler wichtig ist. Und es kann erzwungen werden, projektweit zu sein.
Direktive unterbricht die Abwärtskompatibilität nicht (es sei denn, Sie haben aus einem ziemlich seltsamen Grund ein String-Literal 'use not-null' an einer Position einer Direktive verwendet, was ich wette, niemand tut dies), Außerdem muss TypeScript irgendwann die Rückwärtskompatibilität aufheben, Deshalb hat Semver die Hauptversion definiert, aber das ist eine andere Geschichte.

Ich verwende viele Dateien, die ich nicht erstelle; Woher soll ich wissen, dass diese bestimmte Datei "Use nonnull" hat? Wenn ich 100 Dateien habe, die von anderen Leuten erstellt wurden, muss ich mir merken, welche dieser 100 Dateien ungleich null ist? (Oder jedes Mal, wenn ich eine Variable/Funktion aus einer anderen Datei verwende, muss ich diese Datei öffnen und überprüfen?)

Wenn man sich in einem Projekt mit Richtlinien etc. befindet wie jedes Projekt normal organisiert ist weiß man ... im schlimmsten Fall muss man nur ein bisschen scrollen ... scheint gar nicht so schwer ...

Wo hörst du auf? Wo nennst du es schlecht? Eine Zeichenfolge oder ein Schlüsselwort am Anfang einer Datei sollte das Verhalten einer Datei nicht ändern. "use strikt" wurde zu Javascript hinzugefügt, weil
Zu dieser Zeit gab es keine große Übermenge von Javascript (zB Typescript), die tun konnte, was sie wollte.
Es war ein Versuch, die Verarbeitung von JavaScript zu beschleunigen (was in meinen Augen der einzige Grund ist, warum es entschuldbar ist). "use strict" wurde nicht gewählt, weil es richtig war, sondern weil die Browser nur so auf die Anforderungen der Entwickler eingehen konnten. Ich würde es hassen, wenn Typskript 1 (dann 2, dann 3, dann 4) andere Direktiven hinzufügt, die die Funktionsweise der Sprache als Zeichenfolgen, die in einem beliebigen Bereich deklariert werden, grundlegend ändern und sich auf andere beliebige Bereiche auswirken. Es ist wirklich schlechtes Sprachdesign. Ich würde mich freuen, wenn "use strict" in Typescript nicht existiert, sondern ein Compiler-Flag wäre (und Typescript es in jede Datei/jeden Bereich ausgibt, die es benötigt).

'use strict' wurde eingeführt, um das Sprachverhalten zu ändern und die Retro-Kompatibilität aufrechtzuerhalten, genau das gleiche Problem, mit dem wir jetzt konfrontiert sind, und wird mit den es6-Modulen mehr oder weniger verschwinden (und so könnte use not null mit der nächsten großen Version von typescript verschwinden) .

Javascript ist kein "non-nullable type system", Typescript ist kein "non-nullable type system". Die Einführung der Möglichkeit, Typen ohne NULL-Werte zu deklarieren, bedeutet nicht, dass das gesamte System "nicht-nullierbar" ist. Es ist nicht der Sinn von Typescript.

Dass Sätze für mich keinen Sinn haben, JavaScript hat kein statisches Typsystem, also wie gesagt die Tatsache, dass man null einer Variablen zuweisen kann, die vor string kann leicht verglichen werden darauf, dass Sie einer Variablen, die zuvor ein String war, 5 zuweisen können.
Der einzige Unterschied besteht darin, dass TypeScript, ein für JavaScript entwickelter Typprüfer, meinungsgemäß null als alles zuweisbar betrachtet und nicht als 5 .

In Ihrem Beispiel verlieren wir einige Informationen zwischen den Dateien. Für mich ist dies ein akzeptabler Kompromiss, aber ich kann Ihre Bedenken verstehen.

Wir scheinen uns im Kreis zu streiten. Wenn ich für Sie keinen Sinn ergebe, dann tut es mir leid, aber Sie scheinen immer wieder die gleichen Probleme anzusprechen, und ich antworte wiederholt darauf.

Ja, ich stimme zu, ich bezweifle, dass es diesbezüglich einen Konsens geben wird. Ich denke ehrlich, dass ich am Ende einfach den Compiler abspalten und einen Nicht-Null-Typ für mich hinzufügen werde, in der Hoffnung, dass er eines Tages im Hauptcompiler oder in diesem Fluss landet reif genug, um benutzbar zu sein.

@Griffork übrigens, ich denke, die Idee von @spion funktioniert. Die Idee ist, zu sagen, dass string ohne implizite Nullablesung number | null immer bei der ersten Überladung vorherrschen würde, also in beiden Fall foo(null) ist string .

@RyanCavanaugh denkst du, es könnte funktionieren?

@fdecampredon

  1. "use strict" wurde eingeführt, um Javascript zu optimieren. Es musste von Browsern verwendet werden , also musste es im Code sein. Und es musste abwärtskompatibel sein, also musste es eine nicht-funktionale Anweisung sein.
    Typen, die keine NULL-Werte zulassen, müssen von Browsern nicht verwendet werden, daher muss keine Direktive im Code vorhanden sein.
  2. Entschuldigung, ich bezog mich auf etwas, das ich vergessen hatte zu posten, hier ist es:

Laut Facebook : "In JavaScript konvertiert null implizit in alle primitiven Typen; es ist auch ein gültiger Bewohner jedes Objekttyps."
Der Grund für mich, dass non-nullable explizit sein soll (in jeder Aktion, die ein Entwickler macht), liegt darin, dass der Entwickler durch die Verwendung von non-nullable anerkennt, dass er von der Funktionsweise von Javascript abweicht und dass ein Typ nicht-nullierbar ist ist nicht garantiert (da es viele Dinge gibt, die dazu führen können, dass dies schief geht).
Eine Eigenschaft, die keine NULL-Werte zulässt, ist eine Eigenschaft, die niemals gelöscht werden kann.

Ich benutze Typescript schon seit längerer Zeit. Es fiel mir anfangs schwer, meinen Chef davon zu überzeugen, von Javascript auf Typescript umzusteigen, und es war ziemlich schwer für mich, ihn davon zu überzeugen, uns aktualisieren zu lassen, wenn es eine bahnbrechende Änderung gab (auch wenn es nicht einfach ist). Wenn ES:Harmony von Browsern unterstützt wird, möchten wir es wahrscheinlich verwenden. Wenn in Typescript zwischen jetzt und wenn Typescript ES:Harmony unterstützt (insbesondere eine so weit verbreitete wie nicht-nullfähige) eine grundlegende Änderung auftritt, bezweifle ich, dass ich dieses Argument gewinnen würde. Ganz zu schweigen von der Menge an Zeit und Geld, die es uns kosten würde, alle unsere Entwickler umzuschulen (ich bin unsere "Typescript-Person", es ist meine Aufgabe, unsere Typescript-Entwickler zu schulen).
Daher bin ich wirklich gegen die Einführung von Breaking Changes.
Es würde mir nichts ausmachen, Nicht-Null-Werte selbst zu verwenden, wenn es eine Möglichkeit gäbe, sie in zukünftigen Code einzuführen, ohne alten Code zu zerstören (dies ist der Grund für meinen Vorschlag). Im Idealfall würde schließlich unser gesamter Code konvertiert, aber es würde uns deutlich weniger Zeit und Geld kosten, um Leute zu schulen und Bibliotheken von Drittanbietern zu verwenden.

Ich mag die Idee, Symbole (gemäß meinem Vorschlag) anstelle einer Direktive pro Datei zu verwenden, da sie sich ohne Kontextverlust verbreiten können.

@Griffork Ok wie gesagt, ich verstehe deine Bedenken, ich hoffe du verstehst meine.
Jetzt bin ich nicht der gleichen Meinung, Technologie ändert sich, nichts ist stabil (das ganze Drama um Winkel 2 hat das bewiesen), und ich zerbreche lieber Dinge und habe bessere Werkzeuge, als bei dem zu bleiben, was ich habe, das denke ich in Langfristig gewinne ich dadurch Zeit und Geld.
Nun, da wir beide vorhin geschlossen haben, bezweifle ich, dass wir hier einen Konsens erzielen werden, ich bevorzuge die Richtlinie, Sie bevorzugen Ihren Vorschlag.
Wie auch immer, wie gesagt, ich werde für meinen Teil einfach versuchen, den Compiler abzuspalten und einen Nicht-Null-Typ hinzuzufügen, da ich denke, dass dies keine große Änderung im Compiler ist.
Danke für die Diskussion war interessant

@fdecampredon

Danke für die Diskussion, das war's

Vorausgesetzt, du meintest erhellend (auch interessante Werke :D), hat mir die Debatte auch Spaß gemacht :).
Viel Glück mit deiner Gabel, und ich hoffe, wir bekommen eine TS-Person hier, die zumindest einige unserer Vorschläge verwirft (damit wir wissen, was sie definitiv nicht umsetzen wollen).

Um meine vorgeschlagene Lösung für das von erläutern , weist der Nulltyps und des --noImplicitNull Flags ein geändertes Verhalten auf, wenn es ohne das Flag ausgeführt wird:

Immer wenn eine Typüberladung |null (wie im angegebenen Beispiel):

function fn(x: string): number;
function fn(x: number|null): string;

der Typechecker nimmt jede Überladung und erstellt neue Varianten mit (implizitem) null an dieser Argumentposition, die am Ende der Liste hinzugefügt wird:

function fn(x: string): number;
function fn(x: number|null): string;
// implicit signature added at the end, caused by the first overload
function fn(x: null): number;

Dies ergibt dieselbe Interpretation wie die strikte --noImplicitNull Version: Wenn eine Null übergeben wird, ist die erste übereinstimmende Überladung die explizite number|null . Effektiv macht es explizite Nullen stärker als implizite.

(Ja, ich weiß, dass die Anzahl der impliziten Definitionen exponentiell mit der Anzahl der Argumente wächst, die |null Überladungen haben - ich hoffe, es gibt eine schnellere Implementierung, die Nullen bei einem zweiten Durchgang "zulässt", wenn keine anderen Typen vorhanden sind passen, aber ich glaube nicht, dass es ein Problem sein wird, da ich erwarte, dass diese Situation selten ist.)

So formuliert denke ich, dass die Änderung hoffentlich vollständig abwärtskompatibel ist und sich anmeldet.

Hier sind einige weitere Ideen zum restlichen Verhalten:

Das Zuweisen von Null oder das Nichtinitialisieren von Werten ist auch für Werte jeglichen Typs zulässig, wenn das Flag nicht übergeben wird.

Wenn das Flag aktiviert ist, ist es zulässig, Werte nicht initialisiert (nicht zugewiesen) oder null zu belassen, bis sie an eine Funktion oder einen Operator übergeben werden, der erwartet, dass sie nicht null sind. Bei den Klassenmitgliedern bin ich mir nicht sicher: sie würden es auch

  • müssen am Ende des Konstruktors initialisiert werden, oder
  • Die Initialisierung könnte verfolgt werden, indem überprüft wird, ob Membermethoden, die sie vollständig initialisieren, aufgerufen wurden.

Explizite Null-Unions verhalten sich in beiden Modi ähnlich wie alle anderen Unions. Sie würden eine Wache benötigen, um den Gewerkschaftstyp einzugrenzen.

Ich habe gerade _alle_ die Kommentare oben gelesen und das ist eine Menge. Es ist enttäuschend, dass wir nach so vielen Kommentaren und 5 Monaten immer noch über die gleichen Punkte diskutieren: Syntax und Flags...

Ich denke, viele Leute haben starke Argumente für den Wert der statischen Nullprüfung durch den Compiler in großen Codebasen vorgebracht. Ich werde nichts mehr sagen (aber +1).

Bisher drehte sich die Hauptdebatte um die Syntax und das Kompatibilitätsproblem mit Tonnen von existierendem TS-Code und TS-Bibliotheksdefinitionen. Ich kann verstehen, warum die Einführung eines Nulltyps und der Flow-Syntax ?string am saubersten und natürlichsten erscheint. Aber dies ändert die Bedeutung von string und ist daher eine _riesige_ Breaking Change für _allen_ bestehenden Code.

Die oben vorgeschlagenen Lösungen für diese bahnbrechende Änderung bestehen darin, Compiler-Flags einzuführen ( 'use strict'; wurde als Präzedenzfall angeführt. Wir sollten sie nicht wiederholen, weil TS vergangene Fehler von JS geerbt hat.

Deshalb denke ich, dass die "not null"-Annotation ( string! , !string , ...) der einzige Weg ist und ich werde versuchen, ein neues Argument in die Diskussion einzubringen: vielleicht ist es nicht so schlecht.

Niemand möchte Pony ( ! ) überall im Code haben. Aber das müssen wir wahrscheinlich nicht. TS folgert viel über das Tippen.

1. Lokale Variablen
Ich erwarte nicht, meine lokalen Variablen mit Pony zu versehen. Es ist mir egal, ob ich einer lokalen Variablen null zuweist, solange ich sie auf sichere Weise verwende, was TS durchaus behaupten kann.

var x: string;  // Nullable
x.toString();  // Error: x can be undefined or null
x = "jods";
x.toUpperCase();  // Fine.
notNull(x);  // Fine

Ziemlich oft verwende ich einen Initialisierer anstelle eines Typs.

var x = "jods";  // x: string (nullable)
notNull(x);  // Fine
x = null;  // Fine
notNull(x);  // Error

2. Rückgabewerte der Funktion
Sehr oft gebe ich den Rückgabewert nicht an. So wie TS den Typ ableitet, kann es ableiten, ob NULL-Werte zulässig sind oder nicht.

function() /* : string! */ {
  return "jods";
}

BEARBEITEN: schlechte Idee wegen Vererbung und Funktionsvariablen, siehe Kommentar von @Griffork unten
Wenn ich einen Rückgabetyp angebe, ist TS durchaus in der Lage, die Annotation "nicht null" für mich hinzuzufügen.

Wenn Sie die abgeleitete NULL-Zulässigkeit überschreiben möchten (z. B. weil eine abgeleitete Klasse möglicherweise null zurückgibt, Ihre Methode dies nicht tut), geben Sie den Typ explizit an:

function f() : string {  // f is nullable although this implementation never returns null.
  return "abc";
}

3. Funktionsparameter
Genauso wie Sie einen Typ explizit angeben müssen, _müssen__ Sie hier angeben, ob Sie Nullparameter nicht akzeptieren:

function (x: string!) { return x.toUpperCase(); } // OK
function (x: string) { return x.toUpperCase(); } // Error

Um Kompilierungsfehler in alten Codebasen zu vermeiden, brauchen wir ein neues Flag "Null-Dereferenzierung ist ein Fehler", genau wie wir "Keine impliziten" haben. _Dieses Flag ändert nichts an der Compileranalyse, sondern nur was als Fehler gemeldet wird!_
Ist das viel Pony hinzuzufügen? Schwer zu sagen, ohne Statistiken über echte, große Codebasen zu erstellen. Vergessen Sie nicht, dass optionale Parameter in JS alltäglich sind und alle nullablesbar sind, sodass Sie diese Annotation benötigen würden, wenn 'nicht null' die Standardeinstellung wäre.
Dieser Knall ist auch eine schöne Erinnerung daran, dass Null für Anrufer nicht akzeptiert wird, im Einklang mit dem, was sie heute über string wissen.

4. Klassenfelder
Dies ist ähnlich zu 3. Wenn Sie ein Feld lesen müssen, ohne auf seinen Inhalt schließen zu können, müssen Sie es bei der Deklaration als nicht-nullfähig markieren.

class C {  x: string!; }

function(c: C!) // : string! is inferred
{ return c.x; } // OK, but annotations are required

Wir könnten uns eine Abkürzung für Initializer vorstellen, vielleicht:

class C {
  x! = "jods"; // Note the bang: x is inferred as !string rather than just string.
}

Eine ähnliche Kurzform ist wahrscheinlich für Nullable-Eigenschaften wünschenswert, wenn Sie sagen, dass not-null der Standardwert bei einem Initialisierer ist.
Auch hier ist es schwer zu sagen, ob die meisten Felder im vorhandenen Code nullable sind oder nicht, aber diese Anmerkung entspricht sehr den Erwartungen, die Entwickler heute haben.

5. Erklärungen, .d.ts
Der große Vorteil ist, dass alle vorhandenen Bibliotheken funktionieren. Sie akzeptieren Null- und Nicht-Null-Werte als Eingaben und es wird davon ausgegangen, dass sie einen möglicherweise Null-Wert zurückgeben.
Zuerst müssen Sie einige Rückgabewerte explizit in "nicht null" umwandeln, aber die Situation wird sich verbessern, da die Definitionen langsam aktualisiert werden, um anzuzeigen, wann sie nie null zurückgeben. Das Annotieren der Parameter ist sicherer, aber für eine erfolgreiche Kompilierung nicht erforderlich.

Ich denke, es kann nützlich sein, den "not-null" -Zustand eines Werts anzugeben, aus dem der Compiler nicht ableiten kann (z könnte schön sein:

var a: { s: string } = something(); // Notice s is nullable.
notNull(a.s); // Error
notNull(<!>a.s);  // OK, this is a shorthand "not-null" cast, because the dev knows about something().

Ich denke, die Möglichkeit, dem Compiler auf einfache Weise darauf hinzuweisen, dass etwas nicht null ist, ist wünschenswert. Dies gilt standardmäßig auch für not-null, obwohl es schwieriger ist, mit einer einfachen, logischen Syntax zu kommen.

Entschuldigung, das war ein sehr langer Beitrag. Ich habe versucht, die Idee zu demonstrieren, dass wir selbst bei "standardmäßig null" nicht unseren gesamten Code mit Anmerkungen versehen müssten: TS kann ohne unsere Hilfe auf die Korrektheit des meisten Codes schließen. Die beiden Ausnahmen sind Felder und Parameter. Ich denke, das ist nicht übermäßig laut und sorgt für eine gute Dokumentation. Ein starker Punkt für diesen Ansatz ist, dass er abwärtskompatibel ist.

Ich stimme den meisten Ihrer Aussagen zu @jods4 zu, aber ich habe einige Fragen und Bedenken:

2) Wenn der Compiler bei Rückgabetypen automatisch nullable in nicht nullable konvertieren kann, wie können Sie dies überschreiben (zB für Vererbung oder Funktionen, die ersetzt werden)?

3 & 5) Wenn der Compiler nullable in non nullable konvertieren kann, dann kann sich eine Funktionssignatur in einer Codedatei jetzt anders verhalten als dieselbe Funktionssignatur in einer d.ts-Datei.

6) Wie funktioniert/sieht der Knall bei der Verwendung von Union-Typen aus?

2) Guter Fang. Ich glaube nicht, dass es mit Vererbung (oder "Delegierten") in der Tat gut funktionieren würde.
Meine Erfahrung mit TS ist, dass wir fast nie explizit Rückgabewerte von Funktionen eingeben. TS findet sie heraus und ich denke, TS kann den null/nicht null-Teil herausfinden.

Die einzigen Fälle, in denen wir sie explizit angeben, ist, wenn wir ein Interface oder eine Basisklasse zurückgeben möchten, zB aus Gründen der Erweiterbarkeit (einschließlich Vererbung). Die Nullbarkeit scheint dieser Beschreibung recht gut zu entsprechen.

Lassen Sie uns meinen obigen Vorschlag ändern: Ein explizit typisierter Rückgabewert ist _nicht_ abgeleitet und nicht nullable, wenn er nicht so deklariert ist. Dies funktioniert gut für die von Ihnen vorgeschlagenen Fälle. Es bietet eine Möglichkeit, den Compiler zu überschreiben, der "nicht null" auf einen Vertrag ableitet, der in untergeordneten Klassen null sein könnte.

3 & 5) Mit der obigen Änderung ist das nicht mehr der Fall, oder? Die Funktionssignatur in Code und Definitionen verhalten sich jetzt genau gleich.

6) Interessante Frage!
Dieser ist in der Tat nicht großartig, besonders wenn Sie der Mischung Generika hinzufügen. Der einzige Ausweg, der mir einfällt, ist, dass alle Teile ungleich null sein müssen. Wahrscheinlich benötigen Sie aufgrund von Generika sowieso ein NULL-Typ-Literal, siehe mein letztes Beispiel.

var x: number | string!; // compiler error
var x: number | string; // x can be null
var x: number! | string!; // x cannot be null
function f<T>() : number | T; // f can be null
function f<T>() : number! | T; // f is nullable if T is nullable
function f<T, G>(): T | G; // f is nullable if T or G is nullable
function f<T>(): T | null; // f is nullable even if T is not nullable.
function f<T>(): T!; // Whatever T is, f never returns null.

// Generics constraint option 1
function f<T!>(x: T!, y: T): T!; // T: not nullable type, x: not-null, y: null, f: not-null
function f<T!>(x: T!, y: T): T;  // T: not nullable type, x: not-null, y: null, f: null

// Generics constraint option 2
function f<T!>(x: T, y: T | null): T; // same as option 1.1
function f<T!>(x: T, y: T | null): T | null;  // same as option 1.2

Beachten Sie zur Diskussion, dass Sie, wenn Sie sich standardmäßig für Nicht-Null-Typen entschieden haben, auch eine neue Syntax erfinden müssten: Wie kann man ausdrücken, dass ein generischer Typ keine Nullwerte zulassen darf?
Und umgekehrt, wie würden Sie angeben, dass eine Funktion selbst dann, wenn der generische Typ T null enthält, nie T, aber nie null zurückgibt?

Typliterale sind in Ordnung, aber auch nicht sehr zufriedenstellend: var x: { name: string }! , besonders wenn sie sich über mehr als eine Zeile erstrecken :(

Mehr Beispiele:
Array von Nicht-Null-Zahlen number![]
Nicht null-Array von Zahlen: number[]!
Nichts kann null sein: number![]!
Generika: Array<number!>[]

2, 3 & 5) Ich stimme der Änderung des Vorschlags zu, das scheint zu funktionieren.
6)

function f<T>() : number | T; // f can be null
function f<T>() : number! | T; // f is nullable if T is nullable

Ich gehe davon aus, dass hier Sie Mittelwert f null zurück, nicht , dass f auf null zugeordnet werden kann.

Was halten Sie für Gewerkschaften von der folgenden Syntax, die eine weitere verfügbare Option ist? (sieht ein bisschen albern aus, ist aber in manchen Fällen prägnanter/einfacher zu folgen).

var x = ! & number | string;

Um anzuzeigen, dass keines von beiden NULL-fähig ist.


Bei Typliteralen würde ich sagen, dass Sie sowohl am Anfang als auch am Ende des literalen Ausdrucks den Knall setzen können, da var x: !{name: string} meiner Meinung nach einfacher zu handhaben ist, als es am Ende zu haben.

@jods4 haben Sie über meinen vorgeschlagenen Fix "explizite Null hat Priorität vor impliziter Null" gelesen? Das kümmert sich um das Thema der unterschiedlichen Semantik, denke ich.

@Griffork
Ja natürlich, "ist nullable" war eine schlechte Formulierung für "kann null zurückgeben".

Was mich denken lässt, dass function f() {} eigentlich eine Deklaration von f , die später gesetzt werden kann... wollen wir ausdrücken, dass f niemals auf null gesetzt wird? ? Wie function f!() {} ? Das geht meiner Meinung nach zu weit. Ich denke, der Typprüfer sollte davon ausgehen, dass dies immer der Fall ist. Es gibt andere ungewöhnliche Randfälle, die es sowieso nicht bewältigen kann.

Was die neue Option angeht, stimme ich zu, dass sie etwas albern aussieht und ein zusätzliches Symbol in die Typdeklaration einführt: & , mit einem einzigen Zweck ... Das scheint mir keine gute Idee zu sein. Und in mehreren Sprachen bindet & AND mit einer höheren Priorität als | OR was hier nicht der Fall ist.

Den Knall vor die Typen zu setzen ist eine mögliche Alternative. Ich denke, wir sollten versuchen, alle Beispiele neu zu schreiben, um zu sehen, was besser aussehen könnte. Ich denke, diese Feldverknüpfung, die ich vorgeschlagen habe, wird ein Problem sein:

class C {
  name! = "jods";  // Field inferred string, but marked not nullable.
  // Maybe we could do that instead, which works with "!T" convention:
  name : ! = "jods";
  // That kind of make sense with the proposed <!> cast.
}

@spion
Ja, ich habe es gesehen.

Zuerst bin ich mir nicht sicher, ob es das Problem wirklich löst. Welche Überladung bindet also mit Ihrer neuen Definition an fn(null) ? Die 'generierte', die number zurückgibt? Ist das nicht unvereinbar mit der Tatsache, dass die zweiten Überladungen auch null akzeptieren, aber string ? Oder ist es ein Compilerfehler, weil Sie keine Überladungsauflösung durchführen können?

Zweitens bin ich mir ziemlich sicher, dass wir viele andere Probleme lösen können. Ich denke, das Ändern der globalen Bedeutung des Quellcodes auf Knopfdruck kann einfach nicht funktionieren. Nicht nur in .d.ts , sondern die Leute lieben es, Code wiederzuverwenden (wiederverwenden von Bibliotheken oder sogar Kopieren und Einfügen). Ihr Vorschlag ist, dass die semantische Bedeutung meines Quellcodes vom Compiler-Flag abhängt, und das scheint mir eine sehr fehlerhafte Aussage zu sein.

Aber wenn Sie beweisen können, dass Code in allen Fällen korrekt kompiliert und ausgeführt wird, bin ich dafür!

@jods4

In beiden Fällen wird die 2. Überlast verwendet

function fn(x: string): number;
function fn(x: number|null): string; 

weil die zweite explizite Null Vorrang vor der impliziten (ersten) hat, egal welches Flag verwendet wird. Daher ändert sich die Bedeutung nicht basierend auf dem Flag.

Da der Nulltyp derzeit nicht existiert, werden sie zusammen eingeführt und die Änderung wird vollständig abwärtskompatibel sein. Alte Typdefinitionen funktionieren weiterhin normal.

@spion
Was war also mit dem impliziten function fn(x: null): number; los? Wenn die 2. Überladung immer Vorrang hat, egal welches Flag verwendet wird, hat dieser ganze "Fix" überhaupt keine Wirkung?

Andere Frage: Heute haben alle lib-Definitionen das Verhalten "null erlaubt". Bis sie _alle_ aktualisiert sind, muss ich das Flag "implizit null" verwenden. Wie kann ich mit diesem Flag nun eine Funktion deklarieren, die in meinem Projekt einen Parameter ungleich Null annimmt?

// null by default flag turned on because of 3rd party libs.
function (x: string)   // <- how do I declare this not null?
{ return x.toUpperCase(); }

Der Fix hat auf jeden Fall Wirkung. Es macht das Verhalten mit und ohne Flag konsistent, was das oben erwähnte semantische Problem behebt.

Zweitens müssen Sie kein Flag "standardmäßig null" für Bibliotheken von Drittanbietern verwenden. Das Wichtigste an dieser Funktion ist, dass Sie in den allermeisten Fällen _überhaupt_ nichts tun müssen, um die Vorteile zu nutzen.

Nehmen wir an, es gibt eine Bibliothek, die die Wortanzahl für eine Zeichenfolge berechnet. Seine Erklärung lautet

declare function wordCount(s: string): number;

Nehmen wir an, Ihr Code verwendet diese Funktion auf diese Weise:

function sumWordcounts(s1:string, s2:string) {
  return wordCount(s1) + wordCount(s2);
}

Dieses Programm läuft unter beiden Compilern: selbst wenn implizite Nullen deaktiviert sind. Der Code ist vollständig abwärtskompatibel.

Wieso den? Denn der Compiler, wie er heute ist, glaubt bereits, dass Werte nicht überall dort null sind, wo Sie versuchen, sie zu verwenden (auch wenn sie theoretisch null sein können).

Der neue Compiler würde auch davon ausgehen, dass die Werte nicht null sind, wenn Sie versuchen, sie zu verwenden. Es gibt keinen Grund, etwas anderes zu glauben, da Sie nicht angegeben haben, dass der Wert null sein kann, sodass ein Teil des Verhaltens gleich bleibt. Die Verhaltensänderung wird nur wirksam, wenn null zugewiesen (oder Variablen nicht initialisiert werden) und dann versucht wird, diese Werte an eine Funktion wie die obige zu übergeben. Sie erfordern keine Änderungen in anderen Teilen des Codes, die normalerweise Werte verwenden.

Schauen wir uns nun die Implementierung von wordCount

function wordCount(s) {
  if (s == '') return null;
  return s.split(' ').length
}

Hoppla, dieser Typ erzählt nicht die ganze Geschichte. Es ist möglich, dass diese Funktion einen Nullwert zurückgibt.

Das Problem ist genau das. Es ist unmöglich, die ganze Geschichte im aktuellen Compiler zu erzählen, selbst wenn wir es wollten. Sicher, wir sagen, dass der Rückgabewert eine Zahl ist, was implizit besagt, dass er null sein kann: aber der Compiler warnt nie davor, wenn wir versuchen, falsch auf diesen potenziell null-Wert zuzugreifen. Es denkt immer glücklich, dass es eine gültige Zahl ist.

Nach der Änderung von noImplicitNull wir hier genau das gleiche Ergebnis, wenn wir die gleichen Typdefinitionen verwenden. Der Code wird ohne Beanstandungen kompiliert. Der Compiler wird immer noch gerne denken, dass wordCount immer eine Zahl zurückgibt. Das Programm kann immer noch fehlschlagen, wenn wir leere Zeichenfolgen übergeben, genau wie zuvor (der alte Compiler warnt uns nicht, dass die zurückgegebene Zahl null sein könnte, und der neue wird dies auch nicht, da er der Typdefinition vertraut). Und wenn wir dasselbe Verhalten wünschen, können wir es beibehalten, ohne etwas an unserem Code zu ändern. (1)

Aber jetzt werden wir es besser _können_: Wir werden in der Lage sein, eine verbesserte Typdefinition für wordCount zu schreiben:

declare function wordCount(s:string):string|null;

und erhalten eine Compiler-Warnung, wenn wir versuchen, wordCount ohne nach einem Null-Rückgabewert zu suchen.

Das Beste daran ist, dass diese Verbesserung völlig optional ist. Wir können alle Typdeklarationen unverändert beibehalten und erhalten so ziemlich genau das gleiche Verhalten wie jetzt. Die Typdeklarationen werden nicht schlechter (2) - sie können nur in den Fällen verbessert werden, in denen wir es für notwendig halten.


(1): Hier gibt es bereits eine Verbesserung, auch ohne die Typdefinition zu verbessern. Mit den neuen Typdefinitionen können Sie nicht versehentlich null an sumWordcounts und eine Null-Zeiger-Ausnahme erhalten, wenn die wordCount Funktion versucht, .split() diese Null zu setzen.

sumWordcounts(null, 'a'); // error after the change

(2): Okay, das ist gelogen. Die Typdeklarationen werden für eine kleine Teilmenge von Funktionen schlechter: solche, die nullbare Argumente annehmen, die keine optionalen Argumente sind.

Für optionale Argumente ist immer noch alles in Ordnung:

declare function f(a: string, b?:string); 

aber nicht für Argumente, die nicht optional sind und null sein können

declare function f(a: string, b:string); // a can actually be null

Ich würde argumentieren, dass diese Funktionen ziemlich selten sind und die erforderlichen Korrekturen minimal sein werden.

@spion

Der Fix hat auf jeden Fall Wirkung. Es macht das Verhalten mit und ohne Flag konsistent.

Wie können Sie ein vollständiges Beispiel geben? Du hast auch gesagt:

In beiden Fällen wird die 2. Überlast verwendet

Was bedeutet für mich, dass der Fix keine Wirkung hat?

Dieses Programm läuft unter beiden Compilern: selbst wenn implizite Nullen deaktiviert sind. Der Code ist vollständig abwärtskompatibel.

Ja, es wird in beiden Fällen fehlerfrei kompiliert, aber nicht zum gleichen Ergebnis. sumWordCounts() wird in einem Fall als number! und im zweiten Fall als number? eingegeben. Die Semantik des Codes mit einem Schalter zu ändern, ist höchst _nicht_ wünschenswert. Wie zuvor gezeigt, könnte diese Änderung dann globale Auswirkungen haben, zB auf die Auflösung von Überladungen.

Der neue Compiler würde auch davon ausgehen, dass die Werte nicht null sind, wenn Sie versuchen, sie zu verwenden. Es gibt keinen Grund, etwas anderes zu glauben

Nein! Dies ist der springende Punkt: Ich möchte, dass der Compiler einen Fehler auslöst, wenn ich einen potenziell Nullwert verwende. Wenn es nicht null "annimmt", wie ich es beim Programmieren tue, dann ist diese Funktion nutzlos!

Die Verhaltensänderung wird nur wirksam, wenn null zugewiesen (oder Variablen nicht initialisiert werden) und dann versucht wird, diese Werte an eine Funktion wie die obige zu übergeben.

Ich bin mir nicht sicher, ob ich das verstehe, da "Funktion wie oben" tatsächlich Nullen akzeptiert ...

Wenn ich das Ende Ihres letzten Kommentars lese, habe ich den Eindruck, dass wir keine echte statische Nullanalyse erhalten, bis wir den Schalter einschalten, was Sie nicht tun können, bis _alle_ Ihre Bibliotheksdefinitionen behoben sind. :(

Im Allgemeinen ist es schwierig, alle Fälle und wie alles funktioniert, nur mit Worten vollständig zu verstehen. Einige praktische Beispiele wären sehr hilfreich.

Hier ist ein Beispiel und wie ich möchte, dass sich der Compiler verhält:

Angenommen, eine Bibliothek mit Funktionen yes(): string! , die nie null zurückgibt, und no(): string? , die null zurückgeben kann.

Die Definition von .d.ts lautet:

declare function yes(): string;
declare function no(): string;

Ich möchte große Projekte in TS erstellen können, die von statisch überprüften Nullen profitieren und die vorhandenen Bibliotheksdefinitionen verwenden. Außerdem möchte ich in der Lage sein, _progressiv_ zu einer Situation überzugehen, in der alle Bibliotheken mit korrekten Nullheitsinformationen aktualisiert werden.

Mit dem oben vorgeschlagenen Nicht-Null-Modifikator ! kann ich das tun:

function x(s: string!) {  // inferred : string, could be explicit if we want to
  return s.length === 0 ? null : s;  // no error here as s is declared not null
}

x(no());  // error: x called with a possibly null parameter
y(<!>yes());  // no error because of not null cast. When .d.ts is updated the cast can be dropped.

Wie würde das mit Ihrer Idee funktionieren?

Dieser Thread ist eine zu lange Diskussion (130 Kommentare!), was es sehr schwer macht, den von allen genannten Ideen, Vorschlägen und Problemen zu folgen.

Ich schlage vor, dass wir mit unseren Vorschlägen externe Kernpunkte erstellen, einschließlich vorgeschlagener Syntax, was von TS akzeptiert wird, was ein Fehler ist und so weiter.

@spion Da Ihre Idee Compiler-Flags beinhaltet, sollten Sie für jedes

Hier ist das Wesentliche für !T Nicht-Null-Marker:
https://gist.github.com/jods4/cb31547f972f8c6bbc8b

Es ist größtenteils das gleiche wie in meinem obigen Kommentar, mit den folgenden Unterschieden:

  • Mir ist aufgefallen, dass der Compiler den 'nicht-null'-Aspekt von Funktionsparametern sicher ableiten kann (danke an @spion für diesen Einblick).
  • Ich habe @Griffork- Kommentare !T anstelle von T! ausprobiert.
  • Ich habe einige Abschnitte hinzugefügt, zB zum Verhalten von Konstruktoren.

Fühlen Sie sich frei zu kommentieren, zu verzweigen und alternative Vorschläge zu erstellen ( ?T ).
Versuchen wir, dies voranzubringen.

@jods4

In beiden Fällen wird die 2. Überlast verwendet

Was bedeutet für mich, dass der Fix keine Wirkung hat?

  1. Dies impliziert, dass die Überladungsauflösung geändert wird, um die Sprachsemantik für beide Flags konsistent zu machen.

Ja, es wird in beiden Fällen fehlerfrei kompiliert, aber nicht zum gleichen Ergebnis. sumWordCounts() wird als Zahl eingegeben! in einem Fall und Nummer? in dieser Sekunde. Die Semantik des Codes mit einem Schalter zu ändern, ist höchst unerwünscht. Wie zuvor gezeigt, könnte diese Änderung dann globale Auswirkungen haben, zB auf die Auflösung von Überladungen.

  1. Mein Vorschlag enthält kein number! . Der Typ ist in beiden Fällen einfach number . Das bedeutet, dass die Überladungsauflösung weiterhin normal funktioniert, außer mit Nullwerten. In diesem Fall normalisiert das neue Verhalten für explizite Nullwerte, die Vorrang vor impliziten haben, die Semantik auf abwärtskompatible Weise.

Nein! Dies ist der springende Punkt: Ich möchte, dass der Compiler einen Fehler auslöst, wenn ich einen potenziell Nullwert verwende. Wenn es nicht null "annimmt", wie ich es beim Programmieren tue, dann ist diese Funktion nutzlos!

  1. Der Punkt, den ich auszudrücken versuchte, ist, dass es sehr wenig Rückwärtsinkompatibilität des Features gibt (in Bezug auf Typdeklarationen). Wenn Sie es nicht verwenden, erhalten Sie genau das gleiche Verhalten wie zuvor. Wenn Sie es verwenden möchten, um die Möglichkeit auszudrücken, dass ein Wert vom Typ NULL zurückgegeben werden kann, können Sie dies jetzt tun.

Es ist, als ob der Compiler zuvor den Typ string für Werte des Typs object und string , wobei er alle Zeichenfolgenmethoden für alle Objekte zulässt und sie nie überprüft. Jetzt hätte es einen separaten Typ für Object , und Sie können stattdessen diesen Typ verwenden, um anzuzeigen, dass die Zeichenfolgenmethoden nicht immer verfügbar sind.

Ich bin mir nicht sicher, ob ich das verstehe, da "Funktion wie oben" tatsächlich Nullen akzeptiert ...

Schauen wir uns diese Funktion an:

function sumWordcounts(s1:string, s2:string) {
  return wordCount(s1) + wordCount(s2);
}

Unter dem neuen Compiler-Flag könnten Sie es nicht mit Nullwerten aufrufen, zB sumWordCounts(null, null); und das ist der einzige Unterschied. Die Funktion selbst wird kompiliert, weil die Definition von wordCounts besagt, dass sie einen String braucht und eine Zahl zurückgibt.

Wenn ich das Ende Ihres letzten Kommentars lese, habe ich den Eindruck, dass wir keine echte statische Nullanalyse erhalten, bis wir den Schalter einschalten, was Sie nicht tun können, bis alle Ihre Bibliotheksdefinitionen behoben sind. :(

Die überwiegende Mehrheit des Codes befasst sich einfach nicht wirklich mit Nullen oder undefinierten Werten, außer dass überprüft wird, ob sie null/undefiniert sind, und einen Fehler auslöst, um die Weitergabe dieses Werts durch die Codebasis an eine völlig unabhängige Stelle zu verhindern, was es schwierig macht zu debuggen. Es gibt hier und da ein paar Funktionen mit optionalen Argumenten, die erkennbar sind und mit dem neuen Schalter wohl auch entsprechend modelliert werden können. Es ist nicht so oft, dass Funktionen Nullwerte zurückgeben, wie in meinem Beispiel (das ich nur verwendet habe, um eine Aussage über die Funktion zu machen, nicht als repräsentativen Fall).

Was ich sagen möchte ist, dass die überwiegende Mehrheit der Typdefinitionen korrekt bleiben wird, und für die wenigen verbleibenden Fälle, in denen wir die Korrektur vornehmen müssen, könnten wir uns entscheiden, sie nicht zu korrigieren, wenn wir den Nullfall für unwichtig halten oder ändern Sie den Typ entsprechend, wenn es uns interessiert. Das steht ganz im Einklang mit dem Ziel von typescript, immer stärkere Garantien zu bekommen, wann immer wir sie brauchen.


Okay, die vorhandenen Typdefinitionen sind also

declare function yes(): string;
declare function no(): string;

Und nehmen wir an, Ihr Code wurde geschrieben, bevor die Funktion hinzugefügt wurde:

function x(s: string) {
  return s.length === 0 ? null : s;
}

Unter dem neuen Compiler ist das Verhalten genau das gleiche wie beim alten

x(no());  // no error
x(yes());  // no error

Es sei denn, Sie versuchen etwas, von dem der Compiler weiß, dass es null sein könnte, wie das Ergebnis von x()

x(x(no())) // error, x(something) may be null

Sie sehen sich no() und können entweder entscheiden, dass die Fälle, in denen null zurückgegeben wird, selten sind, sodass Sie dies nicht modellieren, oder Sie können die Typdefinition korrigieren.

Sehen wir uns nun an, was mit Ihrem Vorschlag passiert: Jede einzelne Codezeile, die auch nur eine externe Bibliothek berührt, wird unterbrochen. Funktionen mit perfekt gültigen Typdefinitionen wie oben werden ebenfalls abgebrochen. Sie müssen jede einzelne Argumentannotation überall aktualisieren und ! hinzufügen, damit der Compiler aufhört, sich zu beschweren, oder überall Nullprüfungen hinzufügen.

Die Quintessenz ist, dass das Typsystem in TypeScript derzeit falsch ist. Es hat eine doppelte Interpretation von Nullwerten:

Wenn wir versuchen, einer Variablen vom Typ T null zuzuweisen oder null als Argument zu übergeben, wo ein Typ T erforderlich ist, verhält es sich so, als ob der Typ T|null .

Wenn wir versuchen, einen Wert vom Typ T , verhält es sich so, als ob der Typ T und es keine Möglichkeit für null gibt.

Ihr Vorschlag schlägt vor, alle normalen Typen T als T|null ; Meiner schlägt vor, sie nur als T . Beide ändern die Semantik. Beide sorgen dafür, dass sich der Compiler "richtig" verhält

Mein Argument ist, dass die Variante "nur T " viel weniger schmerzhaft ist, als sie aussieht, und mit der überwiegenden Mehrheit des Codes übereinstimmt.

Bearbeiten: Mir ist gerade aufgefallen, dass Ihr Vorschlag darin bestehen könnte, weiterhin Typen ohne "!" zu behandeln. oder "?" genauso wie vorher. Das ist abwärtskompatibel, ja, aber es ist eine Menge Arbeit, um Vorteile zu erzielen (da die überwiegende Mehrheit des Codes einfach keine anderen Nullwerte als das Überprüfen/Werfen verwendet.

@spion

1.Dies impliziert, dass die Überladungsauflösung geändert wird, um die Sprachsemantik für beide Flags konsistent zu machen.

Du behauptest gerade die gleiche Tatsache, aber es ist mir noch unklar, auf welchen Fall es sich bezieht und auf welche Weise.
Deshalb habe ich nach einem konkreten Beispiel gefragt.

1. Mein Vorschlag enthält kein number! . Der Typ ist in beiden Fällen einfach number .

Das ist falsch, ich weiß, dass die Syntax unterschiedlich ist, aber die zugrunde liegenden Konzepte sind dieselben. Wenn ich number! sage, betone ich einen Nicht-Null-Zahlentyp, der in Ihrem Vorschlag einfach number . Und der zweite Fall wäre in Ihrem Fall nicht number , sondern number | null .

Die überwiegende Mehrheit des Codes befasst sich einfach nicht wirklich mit Nullen oder undefinierten Werten, außer dass überprüft wird, ob sie null/undefiniert sind, und einen Fehler auslöst, um die Weitergabe dieses Werts durch die Codebasis an eine völlig unabhängige Stelle zu verhindern, was es schwierig macht zu debuggen. Es gibt hier und da ein paar Funktionen mit optionalen Argumenten, die erkennbar sind und mit dem neuen Schalter wohl auch entsprechend modelliert werden können. Es ist nicht so oft, dass Funktionen Nullwerte zurückgeben, wie in meinem Beispiel (das ich nur verwendet habe, um eine Aussage über die Funktion zu machen, nicht als repräsentativen Fall).

Ich denke, Beschreibungen wie diese machen _viele_ Annahmen und Abkürzungen. Deshalb denke ich, dass wir, um Fortschritte zu machen, zu konkreteren Beispielen mit Code, Syntax und Erklärungen zur Funktionsweise des Systems übergehen müssen. Du sagst:

Die überwiegende Mehrheit des Codes befasst sich einfach nicht wirklich mit Nullen oder undefinierten Werten, außer zu überprüfen, ob sie null/undefiniert sind und einen Fehler ausgeben

Sehr umstritten.

Es gibt hier und da ein paar Funktionen, die optionale Argumente verwenden.

Noch umstrittener. JS-Bibliotheken sind voll von solchen Beispielen.

, die erkennbar sind und mit dem neuen Schalter wohl entsprechend modelliert werden können

Machen Sie einen konkreteren Vorschlag, denn dies ist eine große Abkürzung. Ich denke, ich kann sehen, wohin dies für den tatsächlichen JS-Code führt, aber wie würden Sie ein optionales Argument in einem declare function(x: {}); oder in einem interface "erkennen"?

Es ist nicht so oft, dass Funktionen wie in meinem Beispiel Nullwerte zurückgeben.

Wieder sehr umstritten. Viele Funktionen geben einen null oder undefined Wert zurück: find (wenn das Element nicht gefunden wird), getError (wenn kein Fehler auftritt) und so weiter ... Wenn Sie weitere Beispiele wünschen, schauen Sie sich einfach die Standard-Browser-APIs an, Sie werden _plenty_ finden.

Was ich sagen will, ist, dass die überwiegende Mehrheit der Typdefinitionen korrekt bleiben wird.

Wie Sie meinen vorherigen Kommentaren entnehmen können, bin ich davon nicht überzeugt.

und für die wenigen verbleibenden Fälle, in denen wir die Korrektur vornehmen müssen, könnten wir uns entscheiden, sie nicht zu beheben, wenn wir den Nullfall für unwichtig halten, oder den Typ entsprechend zu ändern, wenn es uns wichtig ist. Das steht ganz im Einklang mit dem Ziel von typescript, immer stärkere Garantien zu bekommen, wann immer wir sie brauchen.

An dieser Stelle erscheint mir diese Aussage nicht trivial. Können Sie konkrete Beispiele nennen, wie das funktioniert? Vor allem der _progressive_ Teil.

Sehen wir uns nun an, was mit Ihrem Vorschlag passiert: Jede einzelne Codezeile, die auch nur eine externe Bibliothek berührt, wird unterbrochen. Funktionen mit perfekt gültigen Typdefinitionen wie oben werden ebenfalls abgebrochen. Sie müssen jede einzelne Argumentannotation überall aktualisieren und ! hinzufügen, damit der Compiler aufhört, sich zu beschweren, oder überall Nullprüfungen hinzufügen.

Dies ist fast völlig falsch. Bitte lesen Sie das Wesentliche, das ich geschrieben habe, sorgfältig durch. Sie werden feststellen, dass tatsächlich nur wenige Anmerkungen erforderlich sind, die nicht null sind. Aber es gibt _one_ Breaking Change, ja.

Angenommen, Sie nehmen eine vorhandene Codebasis, die Bibliotheksdefinitionen verwendet, und versuchen, sie mit meinem Vorschlag zu kompilieren, ohne Code zu ändern:

  • Sie erhalten viele Vorteile der Nullanalyse, auch ohne Anmerkungen (ziemlich ähnlich wie beim Flow-Compiler).
  • Eine einzige Sache wird kaputt gehen: Die Verwendung des Rückgabewerts einer Bibliotheksfunktion, von der Sie wissen, dass sie nicht null zurückgibt.
declare function f(): string; // existing declaration, but f will never return null.
var x = f();
x.toUpperCase();  // error, possibly null reference.

Die langfristige Lösung besteht darin, die Bibliotheksdefinitionen genauer zu aktualisieren: declare function f(): !string;
Die kurzfristige Lösung besteht darin, eine Nicht-Null-Besetzung hinzuzufügen: var x = <!>f(); .
Und um große Projekte mit vielen Abhängigkeiten leichter aktualisieren zu können, schlage ich vor, ein Compiler-Flag ähnlich wie "keine implizite" hinzuzufügen: "mögliche Nullverweise als Warnung behandeln". Dies bedeutet, dass Sie den neuen Compiler verwenden und warten können, bis die Bibliotheken die Definition aktualisiert haben. Hinweis: Im Gegensatz zu Ihrem Vorschlag ändert dieses Flag die Compiler-Semantik nicht. Es ändert nur das, was als Fehler gemeldet wird.

Wie gesagt, ich bin mir nicht sicher, ob wir in dieser Debatte Fortschritte machen. Ich schlage vor, Sie sehen sich meinen Kern an und erstellen einen mit Ihren eigenen Ideen, Codebeispielen und Verhalten unter beiden Zuständen Ihrer Flagge. Da wir alle Programmierer sind, denke ich, wird es klarer. Außerdem sind viele Randfälle zu berücksichtigen. Nachdem ich das getan habe, habe ich jede Menge Fragen für spezielle Situationen, aber es nützt wirklich nichts, über Konzepte und Ideen ohne eine genaue Definition zu diskutieren.

In dieser über 130 Kommentarausgabe werden zu viele Diskussionen über verschiedene Dinge geführt.

Ich schlage vor, wir setzen die _allgemeine_ Diskussion hier fort.

Für Diskussionen über konkrete Vorschläge, die Nicht-Null-Typen in TS implementieren können, schlage ich vor, dass wir neue Themen eröffnen, jedes zu einem einzelnen Entwurfsvorschlag. Ich habe #1800 erstellt, um die Syntax von !T zu diskutieren.

@spion Ich schlage vor, Sie erstellen auch ein Problem für Ihr Design.

Viele Leute wollen Nicht-Null-Typen. Es ist einfach in neuen Sprachen (zB Rust), aber es ist sehr schwer, es in bestehende Sprachen nachzurüsten (die Leute haben lange nach Nicht-Null-Referenzen in .net gefragt – und ich glaube nicht, dass wir jemals bekommen werden Ihnen). Ein Blick auf die Kommentare hier zeigt, dass es sich um ein nicht triviales Problem handelt.
Flow hat mich davon überzeugt, dass dies für TS möglich ist. Versuchen wir es zu verwirklichen!

@jods4 Es tut mir leid, das sagen zu müssen, aber Ihr Vorschlag hat nichts mit Nicht-Null-Typen zu tun, sondern bezieht sich eher auf eine Art Kontrollflussanalyse, die kaum vorhersehbar ist und die Retrokompatibilität vollständig unterbricht (tatsächlich der größte Teil des Codes, der gültig ist, werden 1,4 ts unter der in Ihrem Kern beschriebenen Regel versagen).
Ich stimme der Tatsache zu, dass die Diskussion nirgendwo hinführt und dass 130 Kommentare + vielleicht zu viel sind. Aber vielleicht liegt es daran, dass hier 2 Gruppen von Leuten diskutieren:

  • diejenigen, die denken, dass ein Nicht-Null-Typ Standard sein sollte und einen Weg finden möchten, dies zu erreichen (durch ein Flag oder einen anderen Mechanismus)
  • diejenigen, die keinen Nicht-Null-Typ wollen und einfach versuchen, ihre Einführung zu vermeiden oder sie in eine neue Syntax zu schieben.

Diese beiden Völkergruppen hören sich schon lange nicht mehr gegenseitig zu und am Ende brauchen wir den Standpunkt des TS-Teams, bis dahin werde ich persönlich aufhören, mehr über dieses Thema zu diskutieren.

@fdecampredon
Was den Teil über meinen Kern betrifft, habe ich Ihre Argumente in #1800 kopiert und wenn Sie wirklich interessiert sind, können wir besprechen, warum es dort ein schlechtes Design ist. Ich verstehe Ihren Standpunkt nicht wirklich, aber ich möchte hier nicht diskutieren, deshalb habe ich #1800 überhaupt erst geschaffen.

In Bezug auf diesen Thread stimme ich dir zu, dass die Diskussion nirgendwo hinführt...

Wie Sie meinen vorherigen Kommentaren entnehmen können, bin ich davon nicht überzeugt.

Nun, ich weiß wirklich nicht, wie ich es anders erklären soll - ich habe es schon ein paar Mal erklärt. Lass es mich noch einmal versuchen.

Das kann man jetzt nicht ausdrücken

declare function err():string;

gibt null zurück. Das ist unmöglich, denn mit Typoskript können Sie err().split(' ') immer gerne tun. Sie können nicht sagen "das ist möglicherweise nicht gültig".

Nach dieser Änderung können Sie string in ?string oder string|null ändern

Aber wenn nicht, _du verlierst nichts_, was du vor der Veränderung hattest:

Sie hatten vor der Änderung keine Nullprüfung und keine danach (wenn Sie nichts tun). Ich argumentierte, dass dies meistens abwärtskompatibel ist. Bleibt natürlich an größeren Codebasen zu demonstrieren.

Der Unterschied ist: Sie konnten vor der Änderung keine Nullprüfung erzwingen, aber Sie _können_ danach (nach und nach, zuerst bei den Definitionen, die Ihnen am wichtigsten sind, dann an anderen Stellen).

@fdecampredon Ich diskutiere das Thema immer noch, weil ich das Gefühl habe, dass es viele Missverständnisse über das Problem gibt und darüber, wie "schwer" und "streng" es sein wird, die Funktion zu nutzen. Ich vermute, dass viele der möglichen Probleme stark übertrieben sind. Wir sollten wahrscheinlich versuchen, eine Basisvariante von --noImplicitNull in einer Gabel zu implementieren - ich bin nicht sicher, ob ich in der nächsten Zeit die Zeit finden werde, aber wenn ich es tue, werde ich es versuchen , klingt nach einem lustigen Projekt.

@spion , ich bin alles dafür, dass Nullen mit dem Schlüsselwort null deklariert werden, aber wie verhält es sich in Ihrem Vorschlag zwischen diesem und undefined?

Und @fdecampredon war meine einzige Bitte, keine große Breaking Change zu haben, insbesondere eine, bei der Fehler an der falschen Stelle angezeigt wurden.

Ich vermute, @spion in Ihrem aktuellen Beispiel könnten Sie so ziemlich mit der Codierung von Typescript fortfahren, wie wir es jetzt tun, wenn Sie davon ausgehen, dass eine lokale Variable, wenn sie nicht zugewiesen ist, in nullfähige und Funktionsparameter mit einem ? kann auch null sein. Nun, mit Ausnahme des Beispiels, das Sie zuvor mit den nullablen frühen Funktionsparametern hatten.

@spion
Was ich aus deinem letzten Kommentar verstehe, unterscheidet sich massiv von dem, was ich vorher verstanden habe...

: string ist also "Ihr alter String-Typ, der nie auf Nullheit überprüft wird, wie <any> nie auf Typkorrektheit überprüft wird".

Die neue Syntax : string | null ist abwärtskompatibel, da sie vorher nicht existierte und der Zugriff auf diesen Typ ohne Nullprüfung ein Fehler ist.

Das ist interessant und es ist schwer, alle Implikationen zu begreifen. Ich muss noch einige Zeit darüber nachdenken.

Die erste Frage, die mir in den Sinn kommt, ist, wie Sie Eingabewerte (z. B. Funktionsparameter) einschränken:

declare f(x: string): void;
f(null);

Ist das ok oder ein Fehler? Wenn es in Ordnung ist, wie kann ich es zu einem Fehler machen.

_Ich denke immer noch, dass jede Idee in dieser Diskussion verloren gegangen ist. Wollen Sie nicht eine neue Ausgabe mit Ihrer Idee eröffnen? Wir könnten es dort besprechen._

@jods das wäre ein Fehler:

declare f(x: string): void; //string must not be null.
declare f(x: string|null): void; //string may be null (not sure about undefined here).
declare f(x?: string): void; //I assume x may be null or undefined.

@jods4 Ich glaube nicht, dass wir mehrere Issues zu einem Thema erstellen müssen, dann wird es schwieriger, die Dinge zu verfolgen, da Sie zu 10 verschiedenen Vorschlägen gehen müssen, nur um zu sehen, ob etwas passiert ist / 10 verschiedene abonnieren müssen . Und sie alle müssten in ihren Einleitungen Links zu den anderen Vorschlägen haben, damit jeder, der nach Nicht-Nullbarkeit sucht, nicht nur einen sieht und für ihn stimmt.

@fdecampredon Ich weiß, dass es viele Beiträge und viele ungelöste Dinge gibt, aber das bedeutet nicht, dass wir aufhören sollten, nach einer Lösung zu suchen, die allen gefällt. Wenn es Ihnen nicht gefällt, dass wir trotzdem gerne darüber sprechen und uns erklären, dann können Sie sich gerne vom Thema abmelden.

@Griffork
Aber erst, nachdem Sie die magische Flagge eingeschaltet haben, oder?
Wenn das Flag deaktiviert ist, haben Sie also ungeprüften Code, mit Ausnahme neuer Nullables-Typen, die zuvor nicht existierten; dann, wenn Sie das Flag einschalten, sind alle Typen nicht nullbar und werden überprüft?

Ich denke, das Endergebnis ist wahrscheinlich die beste Lösung. _Aber es zerstört so ziemlich den gesamten Code, der heute existiert._ Stellen Sie sich vor, ich habe 300.000 Codezeilen... Ich muss überall eine Nullable-Anmerkung hinzufügen, wo ein Typ tatsächlich nullablesbar ist, bevor ich das Flag aktivieren kann. Das ist eine riesige Sache.

Wenn ich das richtig verstehe, ist dies nett für neue Projekte (sobald .d.ts aktualisiert wurden), aber sehr schmerzhaft für bestehenden Code.

@Griffork Mein Problem mit diesem einzelnen Problem und seinem langen Kommentarstrang besteht darin, dass es sehr schwierig ist, eine fokussierte Diskussion über einen einzelnen Vorschlag zu führen. Das Abonnieren von 3 oder 4 Ausgaben ist keine große Sache, und Sie haben in jeder einen guten Kontext.

Der ursprüngliche Vorschlag von

Ja, aber jemand, der sich auf diese Diskussion einlässt, sollte alles lesen, was davor kam. Wenn Sie der Meinung sind, dass die Leute nicht alles lesen sollten, was vorher kam (und daher möglicherweise dieselben Vorschläge vorschlagen), _dann_ können wir es aufteilen; aber ich stimme nicht zu. Der Punkt dabei ist, dass wir zwar über verschiedene Details oder verschiedene mögliche Implementierungen sprachen, aber alle über dasselbe sprachen .
Das gleiche Thema, nicht verschiedene Themen.
Die gesamte Konversation ist zentralisiert, und nichts davon wird durch Nichtbeantworten begraben.

Man kann dieses Gespräch auch nicht wirklich nach Implementierungsdetails aufschlüsseln, da viele Vorschläge oft viele verschiedene Details berühren, die derzeit diskutiert werden.

Die Trennung der Vorschläge bedeutet, dass, wenn jemand einen Fehler entdeckt, er in mehrere verschiedene Threads gehen und diese schreiben muss. Die Leute lernen dann nicht aus den Fehlern des anderen, und die Leute haben die Fähigkeit (was sie werden), sich auf eine Diskussion zu beschränken und die in anderen Diskussionen beschriebenen Fehler zu wiederholen.

Der Punkt ist: Die Aufteilung der Konversation führt dazu, dass die Leute viele Beiträge wiederholen müssen, weil die Zuschauer zu faul sind, alles in allen anderen verlinkten Beiträgen zu lesen (vorausgesetzt, alle relevanten Beiträge verlinken alle anderen Beiträge ).

Auch @jods4 ist nur eine bahnbrechende Änderung, wenn Sie etwas null zuweisen.

Imo eine viel weniger wichtige Änderung als Ihr Vorschlag, bei dem jede einzelne Definition, die nicht null sein sollte, ihr zuweisbar sein müsste, überprüft / berührt werden muss, bevor Sie sie verwenden und die Vorteile von nicht nullbaren Typen nutzen können.

Hier ist ein Beispiel dafür, wie der "Upgrade" -Prozess für die Vorschläge von @jods4 und @spion aussehen könnte (vorausgesetzt, alle erforderlichen Flags sind aktiviert ):

function a(arg1: string): string;
a("mystr").toLowerCase(); //errors in <strong i="11">@jods4</strong>'s proposal because a may return null.
a("mystr").toLowerCase(); //fine in <strong i="12">@spion</strong>'s proposal.

a(null).toLowerCase(); //fine in <strong i="13">@jods4</strong>'s proposal because a may accept null.
a(null).toLowerCase(); //errors in <strong i="14">@spion</strong>'s proposal since a doesn't accept null.

Letztendlich sind beide brechende Veränderungen. Sie sind beide genauso mühsam zu debuggen, da Fehler bei der Variablenverwendung statt bei der Variablendeklaration auftreten. Der Unterschied besteht darin, dass ein Fehler auftritt, wenn null etwas zugewiesen ist (@spion), und der andere Fehler, wenn null etwas zugewiesen ist (@jods4). Persönlich weise ich Dingen viel seltener null zu, als wenn ich Dingen keine null @spion für mich weniger eine Änderung, mit der ich arbeiten kann.

Andere Dinge, die ich mag:

  • Variablentypen ändern sich bei mir nicht dynamisch. Wenn ich das wollte, würde ich Flow verwenden:
var s: string;
s.toUpperCase(); // error
var t = (s || "test").toUpperCase(); // ok. Inferred t: string
yes(t);  // ok
t = no();
yes(t);  // error

Persönlich möchte ich einen Fehler, wenn ich no() aufrufe, keine implizite Typänderung.

  • Nullables, die das Wort null anstelle eines Symbols enthalten, sind leichter zu lesen und weniger zu merken. Wirklich ! sollte für "not" und nicht für "not-null" verwendet werden. Ich hasse es, mich daran erinnern zu müssen, was ein Symbol bewirkt, genauso wie ich Akronyme hasse, ich kann sie mir nie merken und sie verlangsamen meine Arbeitsgeschwindigkeit erheblich.

@jods4 Ich weiß, dass die aktuelle Konversation für die Leute schwer aufzuteilen , da wir all diese Punkte schließlich in allen ansprechen müssen, weil die Uninformierte werden die gleichen Fragen stellen wie wir. Das wird dem Gespräch wirklich nicht _helfen_, es wird es nur schwieriger machen, es aufrechtzuerhalten und mitzuhalten.

@Griffork Ehrlich gesagt habe ich alles gelesen, aber ich bezweifle, dass viele Leute es

Es ist jedes Mal eine bahnbrechende Änderung, wenn Sie etwas haben, das NULL-fähig ist (was bedeutet, dass ihm irgendwann NULL zugewiesen wird, sonst wäre es nicht wirklich NULL-fähig). Der Fehler wird gemeldet, wenn null einer Funktion zugewiesen / an eine Funktion übergeben wird, aber die Fehlerbehebung erfolgt bei der Deklaration. Dazu gehören Funktionsdeklarationen, Variablendeklarationen, Felddeklarationen in Klassen... Vieles muss möglicherweise überprüft und geändert werden.

@jods4 die nicht verwandten Unterdiskussionen hätten abgezweigt werden sollen, nicht die eigentlichen Kerninformationen. Aber es ist zu spät, um zurückzugehen und das jetzt zu ändern.

@spion Ich glaube, ich habe eine einfachere Lösung für das @RyanCavanaugh- Problem mit dem Flag '-nonImplicitNull'. Die Idee ist ziemlich einfach, wir müssen nur ?type type|null oder type|undefined nicht zulassen

@fdecampredon dann benötigen Sie zwei Versionen jeder verfügbaren und aktuell gehaltenen Bibliothek.
Es würde auch unseren Prozess beenden, wenn wir ein nicht striktes Projekt zum Ausprobieren von Code und Ausführen von Tests haben, das vom Code eines strengeren Projekts abhängt.

@jods4

declare f(x: string): void;
f(null);

Wenn Sie das vorgeschlagene --noImplicitNull Flag verwenden, wäre dies ein Fehler.

Ich bin noch nicht bereit, einen offiziellen Vorschlag zu eröffnen, ich möchte zumindest noch ein paar Gedankenexperimente machen oder vielleicht versuchen, eine einfachere Version der Idee in einer Gabel zu implementieren.

Mir ist aufgefallen, dass der Vorschlag von !T tatsächlich auch viel Code zerstört. Nicht, weil sich die Semantik bestehender Typen geändert hat, sondern weil die neue Analyse an vielen Stellen zu Fehlern führt.

Also habe ich es geschlossen. Wenn wir viel Code kaputt machen, bevorzuge ich den anderen Ansatz, er scheint sauberer zu sein.

Ich bin jetzt überzeugt, dass es keine Möglichkeit gibt, dieses Feature einzuführen, ohne viel vorhandenen Code zu zerstören. Dies ist immer noch gut für den gesamten Code, der noch geschrieben werden muss, und möglicherweise kann alter Code ohne die Vorteile von Nullprüfungen durch den Compiler weiter kompiliert werden – genau wie es heute der Fall ist.

Ich frage mich, wie die Haltung des offiziellen TS-Teams in Bezug auf den Bruchaspekt davon im Vergleich zu seinen Vorteilen ist.

Es ist sehr unwahrscheinlich, dass wir jemals eine so große Pause einlegen würden wie "alles standardmäßig nicht auf Null setzend machen". Wenn es einen Vorschlag gäbe, der _nur_ in Fällen, die offensichtlich Bugs sind, neue Fehler ausgibt, könnte das auf dem Tisch liegen, aber solche Vorschläge sind schwer zu bekommen :wink:

Wenn der Wirkungsbereich kleiner wäre – Änderungen in der Überladungsauflösung bei der Übergabe von null als Argument, beispielsweise wenn das genaue Verhalten nicht unbedingt sofort offensichtlich ist, könnte dies eine andere Geschichte sein.

:+1: gesunde Person erkannt: @RyanCavanaugh

Wie wäre es mit keinen Änderungen an der bestehenden Funktionsweise, sondern der Einführung eines NULL-Typs für Unions, der Sie zwingt, die Variable zu schützen, bevor Sie sie verwenden (oder sie einer Variablen mit einem Nicht-Null-Typ zuweisen)? Das Standardverhalten erlaubt es weiterhin, Variablen, die normal typisiert wurden, sowie Variablen, die mit null typisiert wurden, Nullen zuzuweisen, jedoch erzwingt eine Funktion, die bei korrekter Markierung null zurückgibt, eine Umwandlung.

Auf diese Weise gibt es keine grundlegenden Änderungen, aber mit bewährten Praktiken können Sie immer noch eine Reihe von Fehlern vom Typisierungssystem sowohl mit abgeleiteter Typisierung als auch mit guten Codierungspraktiken auffangen.

Dann (später vielleicht?) können Sie ein --noImplicitNullCast-Flag hinzufügen, das verhindert, dass Null Variablen zugewiesen wird, die bei der Eingabe keine Null enthalten (dies funktioniert mehr oder weniger wie der Vorschlag von @spion mit aktiviertem Flag).

Ich kann mir vorstellen, dass dies allen normalen Eingaben und dem Hinzufügen des Flags --noImplicitAny nicht allzu unähnlich ist.

@Griffork
Die erste Hälfte des Pakets (füge einen null Typ hinzu und verbiete die Dereferenzierung ohne Guard) ist nicht 100% kompatibel. Sie werden mehr Code knacken, als Sie denken. Bedenken Sie:

Mit dieser neuen Fähigkeit werden viele Lib-Defs aktualisiert, einschließlich der integrierten. Nehmen wir an, sie ändern einige Signaturen so, dass sie explizit null als möglichen Rückgabewert enthalten. Einige Beispiele:

interface Array<T> {
  find(predicate: (T) => bool) : T | null;
  pop() : T | null;
}
interface Storage {
  getItem(key: string) : any | null;
}

Es gibt viele solcher Apis: find gibt undefined wenn kein Array-Element mit dem Prädikat übereinstimmt, pop gibt undefined wenn das Array leer ist, localStorage.getItem oder sessionStorage.getItem beide null wenn der Schlüssel nicht gefunden wurde.

Der folgende Code ist legitim und vorher perfekt kompiliert. Es wird jetzt mit einem Fehler abgebrochen:

var xs: string[];
if (xs.length > 0) return xs.pop().trim();  // error xs.pop() may be undefined (false positive)

var items : { id: number }[];
var selectedId : number;
// Assume we are sure selectedId is amongst items
var selectedItem = items.find(x => x.id === selectedId);
selectedItem.toString(); // error selectedItem may be undefined (false positive)

Dieselbe Idee, wenn Sie etwas aus localStorage holen, von dem Sie wissen, dass es dort ist. Es gibt viel Code wie diesen, und es erfordert jetzt eine Umwandlung (oder eine neue Syntax), um zu kompilieren und dem Compiler mitzuteilen: Angenommen, dies ist nicht null, ich weiß, was ich tue.

Das wird sicher ein paar tatsächliche Fehler enthalten. Aber es wird auch viele falsch positive Ergebnisse enthalten und das sind bahnbrechende Änderungen. In 100.000+ LOC ist das kein triviales Compiler-Upgrade.

Dies bedeutet wahrscheinlich, dass der einzige Ansatz, der funktionieren würde, darin besteht, dass neuer Code von Grund auf neu geschrieben oder migrierter alter Code die Nullprüfung voll ausnutzt; Legacy-Code hat überhaupt keinen Vorteil. Dies wird durch eine Compileroption (--enableNullAnalysis) und ohne "Übergangs"- oder "progressiven" Migrationspfad gesteuert.

Nun ja. Sie können jedoch jederzeit die lib.d.ts für ein Projekt einfrieren oder sich ein altes holen.
Neue Eingaben werden alten Code brechen, das wird sowieso passieren. Fast jede neue Typisierung erfordert bei der Einführung, dass alter Code auf die eine oder andere Weise aktualisiert wird, damit er richtig funktioniert (zB Unions, Generics).
Diese Änderung bedeutet, dass diejenigen, die das Update ignorieren oder ihre Bibliotheks-/Codedefinitionen einfrieren möchten, nicht darunter leiden.
Theoretisch sollte der meiste Code, der diese Funktionen verwendet, sowieso geschützt werden (und befindet sich normalerweise in großen Codebasen) - da sonst Fehler in Ihrem Code lauern. Daher würde diese Änderung eher Fehler abfangen als weit verbreitete Breaking-Changes verursachen.

Bearbeiten
@jods4 Warum haben Sie unsicheren Code als Beispiel verwendet, wenn dies genau die Art von Code ist, die einen Fehler verursachen könnte, den wir durch diese Änderung erkennen

Bearbeiten 2
Wenn Sie _some_ (falschen/unsicheren) Code nicht zerstören, ist es sinnlos, jemals Änderungen an diesem Thema

Aber andererseits, da dies nur ein Syntaxproblem ist, würde alles noch richtig kompilieren, es würden nur überall Fehler auftreten.

_Neues Tippen wird alten Code brechen, das wird sowieso passieren._

Das ist nicht der Fall. Außer in Ausnahmefällen würden wir keine neuen Funktionen hinzufügen, die bestehenden Code brechen. Generics und Unions wurden lib.d.ts nicht so hinzugefügt, dass Verbraucher von lib.d.ts ihre Codebasis aktualisieren müssen, um die neue Version des Compilers verwenden zu können. Ja, die Leute könnten sich dafür entscheiden, eine ältere Version der Bibliothek zu verwenden, aber es ist einfach nicht der Fall, dass wir Sprachänderungen vornehmen werden, die viel vorhandenen Code zerstören (wie es im Wesentlichen bei allen wichtigen Sprachen der Fall ist). Es wird gelegentlich Ausnahmen geben (https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes), aber es werden nur wenige sein, meistens, wenn wir glauben, dass der Code, den wir knacken, nur ein Käfer.

Du kannst aber jederzeit die lib.d.ts für ein Projekt einfrieren oder ein altes holen

Das Einfrieren von lib.d.ts (und allen anderen .d.ts, die ich übrigens verwende) hat sehr starke Auswirkungen. Das bedeutet, dass ich keine Updates für die von mir verwendeten Bibliotheken oder die neue HTML-API erhalten kann. Dies ist nicht auf die leichte Schulter zu nehmen.

Theoretisch sollte der meiste Code, der diese Funktionen verwendet, sowieso geschützt werden (und befindet sich normalerweise in großen Codebasen) - da sonst Fehler in Ihrem Code lauern.

JS hat eine Tradition, in vielen Fällen undefined (oder manchmal null ) zurückzugeben, die in anderen Sprachen einen Fehler auslösen würden. Ein Beispiel dafür ist Array.prototype.pop . In C# würde das Popping von einem leeren Stack werfen. Man könnte also sagen, dass es immer einen gültigen Wert zurückgibt. In Javascript gibt das Popup eines leeren Arrays undefined . Wenn Ihr Typsystem streng ist, müssen Sie sich irgendwie darum kümmern.

Ich wusste, dass Sie das beantworten würden, deshalb habe ich Beispiele geschrieben, die legitimen, funktionierenden Code darstellen. In großen Codebasen finden Sie viele Beispiele, in denen eine API in einigen Situationen möglicherweise null zurückgibt, aber Sie wissen, dass dies in Ihrem speziellen Fall sicher ist (und Sie ignorieren daher alle Sicherheitsprüfungen).

Ich habe mir vor einer Stunde den Mikroaufgabenteil einer Bibliothek angesehen. Der Hauptteil läuft im Wesentlichen darauf hinaus:

class MicroTasks {
  queue: Array<() => void>;

  flushQueue() {
    while (queue.length > 0) {
      let task = queue.pop();
      task();  // error possible null dereference (not!)
     }
  }
}

Es gibt viele Fälle wie diese.

Zu Ihrem Edit 2: ja, hoffentlich _wird_ diese Änderung Fehler abfangen und auf ungültigen Code hinweisen. Ich bin überzeugt, dass es nützlich ist und deshalb möchte ich, dass es irgendwie passiert. Aber das TS-Team wird die Breaking Changes im _gültigen_ Code berücksichtigen. Hier gibt es Kompromisse, die entschieden werden müssen.

@danquirk Fair, dann denke ich, dass dieses Feature nie implementiert werden kann.
@jods4 Ich verstehe, was Sie sagen, aber Nullables können nie implementiert werden, ohne diesen Code zu brechen.

Traurig, soll ich das Thema endlich schließen?

Wahrscheinlich.

Ich denke, wenn wir uns von den Nullable- und Non-Nullable-Argumenten fernhalten...

Das Problem kann mit einer Reihe neuer Funktionen auf nicht aufdringliche Weise angegangen (oder gemildert) werden:

  • Führe das null-typeguard Symbol ein ?<type>
    Wenn ein Typ mit diesem Symbol annotiert ist, ist es ein Fehler, direkt darauf zuzugreifen

``` TypeScript
var foo: ?string;

foo.indexOf('s'); // Fehler
foo && foo.indexOf('s'); // Okay
```

  • Führe eine Flagge ein --nouseofunassignedlocalvar

``` TypeScript
var foo: Zeichenfolge;

foo.indexOf('s'); // Error

foo = 'Bar';

foo.indexOf('s'); // okay
```

Interessante historische Anmerkung : Als ich versuchte, ein verwandtes Problem dafür zu finden, stieß ich im Februar 2013 auf ein altes Problem bei Codeplex , das eine --cflowu Compileroption erwähnte. @RyanCavanaugh , ich frage mich, was mit dieser Flagge passiert ist?

  • Stellen Sie den sicheren Navigationsoperator #16 vor

TypeScript var x = { y: { z: null, q: undefined } }; console.log(x?.y?.z?.foo); // Should print 'null'

In Kombination würden diese Funktionen dazu beitragen, mehr Fehler bei der Verwendung von null abzufangen, ohne tatsächlich bei jedem einen Nervenzusammenbruch zu verursachen.

@NoelAbrahams :
Ihr erster Vorschlag ist im Wesentlichen identisch mit meinem letzten, Sie verwenden nur ein ? statt |null (lesen Sie den Beitrag von

Ihr zweiter Vorschlag hat ein Problem, das zuvor von @RyanCavanaugh (oben) angesprochen wurde:

Flags, die die Semantik einer Sprache ändern, sind eine gefährliche Sache. Ein Problem besteht darin, dass die Auswirkungen möglicherweise sehr nicht lokal sind:
...[schnipsen]...
Die einzig sichere Möglichkeit besteht darin, die Semantik der Zuweisung gleich zu halten und zu ändern, was ein Fehler ist und was nicht von einem Flag abhängt, ähnlich wie es heute mit noImplicitAny funktioniert.

Ich bin mir ziemlich sicher, dass früher eine nicht unähnliche Flagge vorgeschlagen und dann aufgrund des Kommentars von @RyanCavanaugh aufgegeben wurde.

Ihr dritter Vorschlag hat sehr wenig mit dem aktuellen Thema zu tun (es geht nicht mehr um Tipp- und Kompilierzeitfehler, sondern um das Abfangen von Laufzeitfehlern). Der Grund, warum ich dies sage, liegt darin, dass dieses Thema erstellt wurde, um die Notwendigkeit zu

Könnte es möglich sein, einen der vorgeschlagenen Vorschläge zu implementieren und lib.d.ts einfach nicht zu aktualisieren, um die Nullables (und die anderen Libs) zu verwenden? Dann könnte die Community ihre eigenen Versionen mit Nullables pflegen/verwenden, wenn sie dies wünschen?

Bearbeiten:
Insbesondere enthalten alle Bibliotheken die neuesten Informationen, aber ihre Typisierung wird nicht aktualisiert, um Typeguards zu erfordern.

@RyanCavanaugh Ich komme zurück und bespreche das, wenn/wenn ich einen Patch habe, der zeigt, dass es sich überhaupt nicht um eine große Änderung handelt (insbesondere wenn .d.ts-Dateien nicht aktualisiert werden).

@danquirk- Typdefinitionen müssen nicht aktualisiert werden. Sie können "falsch" bleiben und sind meist abwärtskompatibel.

Wie auch immer, es scheint, dass dieses Problem eine verlorene Sache ist.

Übrigens, es gab einen modifizierten Compiler von MSR, der dies unter anderem getan hat - gibt es Papiere mit ihren Ergebnissen? Bearbeiten: gefunden: http://research.microsoft.com/apps/pubs/?id=224900 aber leider bin ich mir nicht sicher, ob es damit zusammenhängt.

@Griffork , ja, ich bin mir sicher, dass fast alles in der einen oder anderen Form besprochen wurde - angesichts der Länge der Diskussion. Meine Zusammenfassung ist praktisch, um die Probleme um null herum durch die Einführung einer Reihe verwandter Funktionen zu mildern.

nicht überall neue Null- oder undefinierte Prüfungen hinzufügen

Das Einfangen nicht zugewiesener lokaler Variablen mildert dies und der sichere Navigationsoperator ist ein vorgeschlagenes ES-Feature, sodass es irgendwann in TS landen wird.

Ich denke auch, dass die Wahrscheinlichkeit, dass dies in TS kommt, gering ist ... :(

Die einzige Lösung, die mir einfällt, besteht darin, null Safety zu einem Opt-in-Compiler-Flag zu machen. Führen Sie zB die neue T | null oder ?T Syntax ein, aber geben Sie keinen _jeden_ Fehler aus, es sei denn, das Projekt stimmt zu. So profitieren neue Projekte von den Vorteilen und auch alte Projekte, die sich dafür entscheiden, ihren Code mit der neuen Funktion kompatibel zu machen. Große Codebasen, die zu groß sind, um leicht angepasst zu werden, erhalten diese Funktion einfach nicht.

Trotzdem gibt es noch einige Probleme, um diese Funktion zum Fliegen zu bringen ...

@NoelAbrahams Entschuldigung, mein Punkt war nicht, dass es ein schlechter Vorschlag war, nur nicht die Antwort, die von der Anstiftung dieser Diskussion gewünscht wurde.
Ich werde diese Funktion mit Sicherheit verwenden, wenn (/wenn) sie nativ wird, sie klingt wirklich gut.

@jods4 hat das gleiche Problem mit dem oben zitierten @RyanCavanaugh- Kommentar (Zuweisungsfehler aufgrund eines Problems an anderer Stelle, wenn ein Flag geändert wird).

Auch hier ist es am einfachsten, einen der anderen Vorschläge (wahrscheinlich @spion 's) zu implementieren und die neuen Typen nicht zu den .d.ts' hinzuzufügen.

@Griffork
Nicht unbedingt, obwohl dies eines der "mehreren Probleme" ist, die noch übrig sind. Das Feature sollte so ausgelegt sein, dass das Aktivieren des Flags _keine_ Programmsemantik ändert. Es sollte nur Fehler auslösen.

Wenn wir zum Beispiel 'not null' !T Design wählen, glaube ich nicht, dass wir dieses Problem haben. Aber das ist alles andere als ein Problem, das gelöst werden kann.

Ich sollte auch darauf hinweisen, dass T | null zwar eine Breaking Change ist, aber ?T es nicht ist.

@jods4 Ich hatte den Eindruck, dass "sich ändernde Semantik" "ändernde Zuweisungsfähigkeit" einschließt, so oder so bestehen meine (und möglicherweise @RyanCavanaugh ) Bedenken hinsichtlich nicht-lokaler Effekte immer noch.

@NoelAbrahams wie?
T | null erfordert einen typeguard, ?T erfordert einen typeguard, sie sind dasselbe mit unterschiedlichen Namen/Symbolen.
Ja, Sie können beide mit einer Flagge "einschalten".
Ich sehe den Unterschied nicht?

@Griffork , so wie ich es sehe, bedeutet ?T einfach "nicht zugreifen, ohne auf Null zu prüfen". Es steht Ihnen frei, diese Anmerkung zu neuem Code hinzuzufügen, um die Prüfung zu erzwingen. Ich betrachte dies eher als eine Art Operator als als Typanmerkung.

Die Annotation T|null unterbricht vorhandenen Code, da sie eine Aussage darüber macht, ob Typen standardmäßig NULL-fähig sind oder nicht.

@jbondc
Interessant... Ich habe mich noch nicht eingehend mit Ihrem Vorschlag befasst, aber ich denke, ich werde es tun.

Im Moment ist die vom Compiler erzwungene Unveränderlichkeit in JS nicht so interessant wie in anderen Sprachen, da das Threading-Modell gemeinsame Daten verbietet. Aber sicherlich etwas zu beachten.

@NoelAbrahams
Alle NULL-erzwingenden Vorschläge sind Breaking Changes. Sogar ?T , die du beschrieben hast. Ich habe mehrere Beispiele gezeigt, warum oben ein paar Kommentare.

Deshalb denke ich, dass der einzige Ausweg, wenn wir dies wollen, darin besteht, es zu einer Compileroption zu machen und dies gut genug zu bedenken, damit das Aktivieren der Option die gemeldeten Fehler ändert, aber nicht das Programmverhalten. Nicht sicher, ob es machbar ist oder nicht.

Damit komme ich eigentlich ganz gut zurecht. Mit TS 1.5 werde ich in der Lage sein, das zu bekommen, was ich will:

function isVoid(item:any): item is void { return item == null; }
declare externalUnsafeFunction(...):string|void

function test() {
  var res = externalUnsafeFunction(...);
  var words = res.split(' '); // error
  if (!isVoid(res)) {
    var words = res.split(' '); // ok
  }
}

jetzt wird die isVoid Prüfung für Rückgabewerte für externalUnsafeFunction oder jede andere Funktion oder Funktionsdefinition erzwungen, bei der ich |void zum Rückgabetyp hinzufüge.

Ich werde immer noch nicht in der Lage sein, Funktionen zu deklarieren, die null/undefiniert nicht akzeptieren, aber es ist möglich, gewissenhaft genug zu sein, um sich daran zu erinnern, lokale Variablen und Klassenmitglieder zu initialisieren.

Weil @NoelAbrahams wir bereits besprochen haben, dass auch mit einem null-Typ null implizit in einen anderen Typ umgewandelt werden muss, es sei denn, ein zukünftiges Compiler-Flag ändert dies.

Es bedeutet auch , dass wir in der Zukunft in einem Compiler - Flag bringen können , dass Lassen Sie sie uns mit Anmerkungen versehen , wo und wann eine Funktion null annehmen kann.

Und ich persönlich hasse die Idee, Symbole zu verwenden, um Typen darzustellen, wenn wir stattdessen Wörter verwenden können.

@spion das ist eigentlich ein guter Punkt. Wenn das derzeit in TS funktioniert, sind wohl alle eingebauten d.ts-Dateien bereits "falsch" , das Hinzufügen des Null-Typs, wie von Ihnen vorgeschlagen, mit meinen vorgeschlagenen Modifikationen ändert nichts.

Da es bereits einigermaßen machbar ist, werde ich meinem Chef vorschlagen, dass wir damit diese Woche beginnen.

Ich möchte davor warnen, dass wir T|void keinesfalls als Syntax sehen, die wir in Zukunft befürchten würden, zu brechen. Es ist fast Unsinn.

@RyanCavanaugh Es ist ungefähr so ​​​​unsinnig wie void === undefined (ein zuweisbarer Wert).

Seufzen.

Unser Code ist zu groß, um zu starten, abhängig von T|void wenn er bald kaputt geht und nicht ersetzt wird. Und ich werde die anderen Programmierer nicht davon überzeugen können, es zu benutzen, wenn es jede Woche kaputt gehen könnte.

Ok, @spion, wenn Sie jemals Ihren Patch

@RyanCavanaugh Unsinn, wirklich? Inwiefern? Und was würden Sie vorschlagen, um Nullable-Typen auszudrücken, die für jede Art von Konsum vor einer Null-/Undefinierten Prüfung verboten sein sollten?

Ich habe wirklich viele Bibliotheken, die davon profitieren würden.

@Griffork es wird void vor 1.5 sicher aus der Union zu entfernen, nicht ohne benutzerdefinierte Typwächter, also wird dies erst nach 1.5 möglich sein (wenn es überhaupt möglich wird)

Unsinn wie in würdest du jemals diesen Code schreiben?

var foo: void;
var bar: void = doStuff();

Natürlich nicht. Was bedeutet es dann, void zu einer Vereinigung möglicher Typen hinzuzufügen? Beachten Sie diesen Teil der Sprachspezifikation, der seit einiger Zeit im Abschnitt zu The Void Type (3.2.4) existiert:

_HINWEIS: Wir könnten erwägen, die Deklaration von Variablen vom Typ Void zu verbieten, da sie keinem nützlichen Zweck dienen. Da Void jedoch als Typargument für einen generischen Typ oder eine generische Funktion zulässig ist, ist es nicht möglich, Void-Eigenschaften oder -Parameter zu verbieten._

Diese Frage:

_Und was würden Sie vorschlagen, um Nullable-Typen auszudrücken, die vor einer Null-/Undefinierten-Prüfung für jede Art von Konsum verboten sein sollten?_

darum geht es in dem ganzen thread. Ryan weist lediglich darauf hin, dass string|void nach der aktuellen Sprachsemantik Unsinn ist und es daher nicht unbedingt sinnvoll ist, eine Abhängigkeit von der Unsinnssemantik einzugehen.

Der Code, den ich oben geschrieben habe, ist afaik vollkommen gültiges TS 1.5, oder?

Ich gebe ein Beispiel, das sicherlich kein Unsinn ist. Wir haben eine (nodejs)-Datenbankbibliotheksfunktion, die wir get() aufrufen, ähnlich wie Linqs Single(), die leider Promise<null> zurückgibt, anstatt einen Fehler (ein abgelehntes Versprechen) auszulösen, wenn das Element nicht gefunden wird. Es wird in unserer gesamten Codebasis an Tausenden von Stellen verwendet und wird wahrscheinlich nicht ersetzt oder bald verschwinden. Ich möchte eine Typdefinition schreiben, die mich und die anderen Entwickler dazu zwingt, vor dem Verbrauch des Werts einen Typwächter zu verwenden, da wir Dutzende von schwer zu verfolgenden Fehlern hatten, die darauf zurückzuführen sind, dass ein Nullwert weit in die Codebasis verschoben wurde, bevor er falsch verwendet wurde.

interface Legacy { 
  get<T>(...):Promise<T|void>
}

function isVoid(val: any): val is void { return val == null; } // also captures undefined

legacy.get(...).then(val => {
  // val.consumeNormally() is a type error here
  if (!isVoid(val)) { 
    val.consumeNormally(); // OK
  }
  else { handle null case }
});

Das sieht für mich völlig vernünftig aus. Das Hinzufügen von void zur Vereinigung führt dazu, dass alle Operationen auf val ungültig sind, wie es sein sollte. Der Typeguard grenzt den Typ ein, indem er das |void und den T Teil belässt.

Vielleicht berücksichtigt die Spezifikation die Auswirkungen von Gewerkschaften mit void nicht?

Verbieten Sie keine void-Variablen! Sie funktionieren als Einheitentypwerte und können verwendet werden
in Ausdrücken (im Gegensatz zu void in C#, das 2 Sätze von allem erfordert: einen
für Aktionen und eine Funktion). Bitte lass die Leere in Ruhe, ok?
Am 27. Januar 2015, 20:54 Uhr, schrieb "Gorgi Kosev" [email protected] :

Der Code, den ich oben geschrieben habe, ist afaik vollkommen gültiges TS 1.5, oder?

Ich gebe Ihnen ein Beispiel, das sicherlich kein Unsinn ist. Wir haben ein
Datenbankbibliotheksfunktion ähnlich der Single() von Linq, die leider
gibt Versprechen zurückstattdessen einen Fehler (ein abgelehntes Versprechen) ausgeben, wenn
der Artikel wird nicht gefunden. Es wird in unserer gesamten Codebasis in Tausenden von verwendet
Orte und wird wahrscheinlich nicht ersetzt werden oder bald verschwinden. Ich möchte schreiben
Typdefinition, die mich und die anderen Entwickler zwingt, einen Typwächter zu verwenden
bevor wir den Wert verbraucht haben, da wir Dutzende von schwer zu verfolgenden Fehlern hatten
stammt von einem Nullwert, der weit in die Codebasis vordringt, bevor er
falsch konsumiert.

Schnittstelle Legacy {
werden(...):Versprechen
}
Funktion isVoid(val: any): val is void { return val == null; } // erfasst auch undefined

Legacy.get(...).then(val => {
// val.consumeNormally() ist ein Fehler
if (!isVoid(val)) {
val.consumeNormally(); // OK
}
else { Null-Fall behandeln}
});

Das sieht für mich völlig vernünftig aus. Können Sie sagen, welcher Teil ist?
Unsinn?

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

Was ich damit sagen will ist, dass T|void im Moment im Wesentlichen bedeutungslos ist, weil wir bereits die Regel haben, dass die Vereinigung eines Typs und seines Untertyps einfach dem Supertyp entspricht, zB Cat|Animal ist äquivalent zu Animal . void als Ersatz für die Werte null / undefined ist nicht kohärent, da null und undefined _bereits in der Domäne von T _ für alle T , was bedeutet, dass sie Untertypen von T

Mit anderen Worten, T|null|undefined , wenn Sie das schreiben könnten, würde bereits in T . void als Beschwörungsformel für null|undefined ist falsch, denn wenn das tatsächlich der Fall wäre, hätte der Compiler T|void in T reduziert.

Ich würde dies persönlich lesen http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf

Die einzigen möglichen Werte für den Void-Typ sind null und undefiniert. Der Void-Typ ist ein Untertyp des Any-Typs und ein Supertyp der Null- und Undefinierten Typen, aber ansonsten hat Void nichts mit allen anderen Typen zu tun.

Wie; in T|void ist T speziell _kein_ ein Supertyp von void weil der einzige Supertyp (gemäß Spezifikation) any .

Edit: Verbieten ist ein anderes Thema.

Edit2: Lesen Sie noch einmal, was Sie gesagt haben, und es ist buchstäblich sinnvoll (so wie die Dokumentation ungültig formuliert hat), aber es macht keinen Sinn in der angenommenen gemeinsamen (oder richtigen) Interpretation der Dokumentation.

@RyanCavanaugh Ich werde die Funktion trotzdem verwenden, bis sie kaputt geht, da sie mir in der Zwischenzeit wahrscheinlich helfen wird, viele Fehler zu finden.

Und ich kann nicht anders, als auf die Ironie der Aussage hinzuweisen, dass es "sinnvoll" ist, T|null|undefined in T zerlegen, während die Existenz von T|void dies nicht tut. (Ich weiß, es geht um die Spezifikation, aber trotzdem...)

@jbondc

Es gibt keine statischen Strukturtypen in ECMAScript, daher ist dieses Argument ungültig. In ECMAScript sind alle Werte vom Typ any daher kann alles an alles übergeben oder an alles übergeben werden. Diese Tatsache sollte nichts über das statische Typsystem von TypeScript bedeuten, das dazu da ist, das nicht vorhandene in JS zu verbessern

Es ist ein bisschen traurig, dass ich die Ironie erklären muss, aber so geht es:

  1. Sie haben einen Wert, der keine Methoden und keine Eigenschaften unterstützt, null
  2. Sie machen ein Typsystem, in dem dieser Wert Variablen jeden Typs zuweisbar ist, von denen die meisten verschiedene Methoden und Eigenschaften unterstützen (zB Zeichenfolgen, Zahlen oder komplexe Objekte var s:string = null )
  3. Sie machen einen void Typ, der diesen Wert korrekt akzeptiert und keine Methoden oder Eigenschaften zulässt, und folglich lässt T|void keine Methoden und Eigenschaften zu.

Die Behauptung ist, dass (3) Unsinn ist, während (2) es nicht ist. Sehen Sie hier den ironischen Teil? Wenn nicht, versuche ich es mit einem anderen Beispiel:

  1. Sie haben Werte, die nur einige Methoden und Eigenschaften unterstützen {}
  2. Sie erstellen ein Typsystem, in dem dieser Wert Variablen jeden Typs zugewiesen werden kann (zB Strings, Zahlen oder komplexe Objekte var s:string = {} ), die einen viel größeren Satz von Methoden und Eigenschaften haben
  3. Sie haben einen {} Typ, der {} Werte korrekt akzeptiert und keine anderen Methoden oder Eigenschaften als ein paar eingebaute zulässt, und T|{} erlaubt natürlich keine anderen Methoden oder Eigenschaften als die eingebaute von {}

Was ist nun Unsinn, (2) oder (3)?

Was ist der einzige Unterschied zwischen den beiden Beispielen? Ein paar eingebaute Methoden.

Schließlich war die Schnittstelle nur eine Typdefinitionsbeschreibung. Es ist keine Schnittstelle, die irgendwo implementiert wird. Dies ist die von der Bibliothek bereitgestellte Schnittstelle, die niemals etwas anderes als Versprechen zurückgibt, aber möglicherweise ein Versprechen für null zurückgibt. Daher ist die Sicherheit auf jeden Fall sehr real.

@spion genau das habe ich mir auch gedacht.

Hier, ich denke, das wird die Dinge ein wenig klären:
Laut Spezifikation :


Was ist nichtig?

Der Void-Typ, auf den das void-Schlüsselwort verweist, stellt das Fehlen eines Werts dar und wird als Rückgabetyp von Funktionen ohne Rückgabewert verwendet.

void ist also nicht null|undefiniert.

Die einzigen möglichen Werte für den Void-Typ sind null und undefiniert

null und undefined sind also void zuweisbar (wie sie so ziemlich allem anderen zuweisbar sind).


Null ist ein Typ.

3.2.5 Der Nulltyp
Der Null-Typ entspricht dem ähnlich benannten primitiven JavaScript-Typ und ist der Typ der Null
wörtlich.
Das Null-Literal verweist auf den einzigen Wert des Null-Typs. Es ist nicht möglich, direkt auf den Null-Typ selbst zu verweisen.


Undefiniert ist ein Typ.

3.2.6 Der undefinierte Typ
Der Typ Undefined entspricht dem ähnlich benannten primitiven JavaScript-Typ und ist der Typ des undefinierten Literals.


Void ist nur ein Supertyp der Null- und Undefinierten _Typen_

. Der Void-Typ ist ein Untertyp des Any-Typs und ein Supertyp der Null- und Undefinierten Typen, aber ansonsten hat Void nichts mit allen anderen Typen zu tun.


@jbondc ist also gemäß der Typescript Language Specification null ein Wert, null ist ein Typ, undefined ist ein Wert, Undefined ist ein Typ und Void (Kleinbuchstaben im Code) ist ein Typ, der ein Supertyp von Null ist und Nicht definiert, aber _wird nicht implizit in einen anderen Typ umgewandelt (außer any ). Tatsächlich ist Void speziell so geschrieben, dass es ein anderes Verhalten als die Typen Null und Undefined aufweist:

Leere:

Der Void-Typ ist ein Untertyp des Any-Typs und ein Supertyp der Null- und Undefinierten Typen, aber ansonsten hat Void nichts mit allen anderen Typen zu tun.

Null:

Der Null-Typ ist ein Untertyp aller Typen, mit Ausnahme des Typs Undefined. Dies bedeutet, dass null als gültiger Wert für alle primitiven Typen, Objekttypen, Unionstypen und Typparameter angesehen wird, einschließlich der primitiven Typen Number und Boolean.

Nicht definiert:

Der undefinierte Typ ist ein Untertyp aller Typen. Dies bedeutet, dass undefined als gültiger Wert für alle primitiven Typen, Objekttypen, Unionstypen und Typparameter angesehen wird.

Jetzt klarer?

Was wir verlangen, ist ein Typ, der eine Wache für Nullen erzwingt, und ein Typ, der eine Wache für Undefinierte erzwingt. Ich verlange nicht, dass null oder undefined anderen Typen nicht zuordenbar ist (das ist zu kaputt), nur die Möglichkeit, sich für zusätzliches Markup zu entscheiden. Verdammt, sie müssen nicht einmal Null oder Undefined (die Typen) heißen.

@jbondc das ist die Ironie: Egal was der TS-Compiler sagt, die null- und undefinierten Werte in JS werden niemals Methoden oder Eigenschaften unterstützen (außer das Auslösen von Ausnahmen). Daher ist es Unsinn, sie jedem Typ zuweisbar zu machen, ungefähr so ​​viel Unsinn, als würde man Strings den Wert {} zuweisen. Der void-Typ in TS enthält keine Werte, _aber_ null oder undefined sind jedem zuweisbar und können daher Variablen vom Typ void zugewiesen werden, also liegt hier noch ein bisschen Unsinn. Und das ist die Ironie - dass sich (über Typenwächter und Gewerkschaften) beide derzeit zu etwas verbinden, das tatsächlich Sinn macht :)

Etwas so Komplexes wie der TS-Compiler wurde ohne Guards geschrieben, darüber sollte man nachdenken.

Es gibt eine ziemlich große Anwendung, die in PHP oder JAVA geschrieben wurde und die die Sprache nicht besser oder schlechter macht.
Ägyptische Pyramiden wurden ohne moderne Maschine gebaut, bedeutet das, dass alles, was wir in den letzten 4500 Jahren erfunden haben, Mist ist?

@jbondc Siehe meinen obigen Kommentar mit der Liste der Probleme im TypeScript-Compiler, die durch Null- und undefinierte Werte verursacht werden (abzüglich des ersten in der Liste).

@jbondc Es soll nicht die Probleme der Welt lösen, es soll ein weiteres Tool sein, das Entwicklern zur Verfügung steht.
Es hätte dieselbe Wirkung und denselben Nutzen wie Funktionsüberladungen, Unions und Typaliase.
Es ist eine weitere Opt-in-Syntax, die es Entwicklern ermöglicht, Werte genauer einzugeben.

Eines der großen Probleme, die erwähnt werden, um Typen nicht-nullfähig zu machen, ist, was mit vorhandenem TypeScript-Code zu tun ist. Hier gibt es zwei Probleme: 1) Vorhandenen Code mit einem neueren Compiler zum Laufen zu bringen, der Nicht-Nullable-Typen unterstützt, und 2) Zusammenarbeit mit nicht aktualisiertem Code – Vermeidung eines Sumpfes im Python 2/3-Stil

Es sollte jedoch möglich sein, ein Tool bereitzustellen, das eine automatisierte Umschreibung von vorhandenem TS-Code durchführt, um ihn zu erstellen, und für diesen begrenzten Fall etwas, das viel besser funktioniert als beispielsweise 2to3 für Python.

Vielleicht könnte eine schrittweise Migration so aussehen:

  1. Führen Sie die Unterstützung für die '?T'-Syntax ein, um einen möglicherweise Nulltyp anzugeben. Dies hat zunächst keine Auswirkung auf die Typprüfung, sondern dient nur der Dokumentation.
  2. Stellen Sie ein Tool bereit, das vorhandenen Code automatisch umschreibt, um '?T' zu verwenden. Die dümmste Implementierung würde alle Verwendungen von 'T' in '?T' umwandeln. Eine intelligentere Implementierung würde prüfen, ob der Code, der den Typ verwendet, implizit davon ausgeht, dass er nicht null ist, und in diesem Fall 'T' verwenden. Bei Code, der versucht, einem Parameter oder einer Eigenschaft, die 'T' erwartet, einen Wert vom Typ '?T' zuzuweisen, könnte dies den Wert in einen Platzhalter einschließen, der (zur Kompilierzeit) bestätigt, dass der Wert nicht null ist - zB let foo: T = expect(possiblyNullFoo) oder let foo:T = possiblyNullFoo!
  3. Wenden Sie das Tool auf alle bereitgestellten Typdefinitionen und diejenigen im DefinitelyTyped-Repository an
  4. Führen Sie eine Compiler-Warnung ein, wenn Sie versuchen, einem Slot '?T' zuzuweisen, der ein 'T' erwartet.
  5. Lassen Sie die Warnungen in freier Wildbahn backen und überprüfen Sie, ob die Portierung überschaubar ist, und sehen Sie, wie die Änderung aufgenommen wird
  6. Machen Sie das Zuweisen von '?T' zu einem Slot, der ein 'T' erwartet, zu einem Fehler in einer neuen Hauptversion des Compilers.

Vielleicht lohnt es sich auch, etwas einzuführen, das die Warnungen für bestimmte Module deaktiviert, um die Verwendung von Code zu erleichtern, auf den das Tool noch nicht angewendet wurde.

Im vorherigen Kommentar gehe ich davon aus, dass es keinen Modus gibt, um die NULL-Zulässigkeit des einfachen 'T' zu wählen, um eine Aufteilung der Sprache zu vermeiden, und dass die Schritte 1-3 allein nützlich wären, auch wenn sich Schritt 6 nie als praktisch erweisen würde.

Meine zufälligen Gedanken:

  1. Der Teil expect() kann kompliziert sein, er müsste gut durchdacht sein. Es gibt nicht nur Zuweisungen, sondern Schnittstellen, Generics oder Parametertypen...
  2. Ich _glaube_ TS hat keine Warnung, nur Fehler. Wenn ich richtig liege, bin ich nicht sicher, ob sie nur für diese Funktion Warnungen einführen werden.
  3. Selbst wenn viele Leute ihren Code aktualisieren, können Unternehmen dies sehr langsam tun oder dies bei großen bestehenden Codebasen sogar nie tun wollen. Dies macht diese Hauptversion zu einer großen bahnbrechenden Änderung, die das TS-Team nicht will.

Trotzdem denke ich immer noch, dass ein "Opt-In"-Flag zum Melden von Nullfehlern eine akzeptable Lösung ist. Das Problem ist, dass der gleiche Code/die gleichen Definitionen das gleiche Verhalten (abzüglich der Fehler) haben sollten, wenn das Flag ausgeschaltet ist, etwas, für das ich noch nicht die Zeit hatte, darüber nachzudenken.

@ jods4 - Für den expect() Teil, mein Denken ist , dass überall dort , wo Sie ?T der Compiler sieht T | undefined , so dass Sie die Regeln für den Umgang mit Union - Typen so weit wiederverwenden könnten möglich.

Ich stimme zu, ich bin mir nicht sicher, wie realistisch ein „Flaggentag“ für TypeScript ist. Abgesehen von einem Opt-in-Flag kann es auch ein Opt-in-Flag sein, das schließlich zu einem Opt-out-Flag wird. Wichtig ist, eine klare Richtung zu haben, was der idiomatische Stil ist.

Etwas anderes, das für diese Diskussion relevant ist – https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf diskutiert Untersuchungen des Chrome-Teams zur Verwendung von Typinformationen (angegeben über TypeScript-Stil). Annotationen) im Compiler, um JS zu optimieren. Es gibt eine offene Frage zur NULL-Zulässigkeit - sie möchten Typen, die keine NULL-Werte zulassen, sind sich aber auch nicht sicher, ob dies möglich ist.

Werde mich hier einfach wieder melden. Ich bin überrascht, dass wir dieses Problem nicht wirklich gelöst haben.

Ich sage, es gibt hier einen Mehrwert. Eine, die nur zur Kompilierzeit anwendbar ist.
Anstatt mehr Code schreiben zu müssen, um einen Wert auf Null zu überprüfen, kann die Deklaration eines Werts als nicht nullwertfähig Zeit und Codierung sparen. Wenn ich einen Typ als 'non-nullable' beschreibe, muss er initialisiert und möglicherweise als nicht-null bestätigt werden, bevor er an eine andere Funktion übergeben wird, die nicht-null erwartet.

Wie ich oben sagte, könnte dies vielleicht einfach durch eine Implementierung von Codeverträgen gelöst werden.

Warum können Sie nicht einfach aufhören, das Null-Literal zu verwenden und alle Orte zu bewachen, an denen?
Nullen können in Ihren Code eindringen? So sind Sie vor null sicher
Referenzausnahme, ohne überall auf null prüfen zu müssen.
Am 20. Februar 2015, 13:41 Uhr, schrieb "electricessence" [email protected] :

Werde mich hier einfach wieder melden. Ich bin überrascht, dass wir uns nicht wirklich gelöst haben
Dieses hier.

Ich sage, es gibt hier einen Mehrwert. Eine, die nur beim Kompilieren anwendbar ist
Zeit.
Anstatt mehr Code schreiben zu müssen, um einen Wert auf Null zu überprüfen, können Sie
Deklarieren eines Werts als nicht nullablesbar kann Zeit und Codierung sparen. Wenn ich a . beschreibe
type als 'non-nullable', dann muss es initialisiert und möglicherweise bestätigt werden
als ungleich null, bevor sie an eine andere Funktion weitergegeben wird, die erwartet
nicht null.

Wie ich oben sagte, könnte dies vielleicht einfach durch einen Code-Vertrag gelöst werden
Implementierung.

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

Nullprüfungen sind wesentlich schneller als undefinierte Prüfungen.
Am 21.02.2015, 11:54 Uhr, schrieb "Aleksey Bykov" [email protected] :

Warum können Sie nicht einfach aufhören, das Null-Literal zu verwenden und alle Orte zu bewachen, an denen?
Nullen können in Ihren Code eindringen? So sind Sie vor null sicher
Referenzausnahme, ohne überall auf null prüfen zu müssen.
Am 20.02.2015 13:41 Uhr, "electricessence" [email protected]
schrieb:

Werde mich hier einfach wieder melden. Ich bin überrascht, dass wir uns nicht wirklich gelöst haben
Dieses hier.

Ich sage, es gibt hier einen Mehrwert. Eine, die nur beim Kompilieren anwendbar ist
Zeit.
Anstatt mehr Code schreiben zu müssen, um einen Wert auf Null zu überprüfen, können Sie
Deklarieren eines Werts als nicht nullablesbar kann Zeit und Codierung sparen. Wenn ich a . beschreibe
Geben Sie als 'non-nullable' ein, dann muss es initialisiert werden und möglicherweise
behauptete
als ungleich null, bevor sie an eine andere Funktion weitergegeben wird, die erwartet
nicht null.

Wie ich oben sagte, könnte dies vielleicht einfach durch einen Code-Vertrag gelöst werden
Implementierung.

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


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

Mein Punkt, dass diese Überprüfungen unnötig sind (wenn das Null-Schlüsselwort gesperrt ist
und alle anderen Orte, an denen es eindringen kann, werden bewacht).

Wieso den? 1. Der Code ohne Checks läuft deutlich schneller als Code
mit Prüfungen auf Nullen. 2. Es enthält weniger Wenns und ist daher besser lesbar
und wartbar.
Am 20. Februar 2015, 19:57 Uhr, schrieb "Griffork" [email protected] :

Nullprüfungen sind wesentlich schneller als undefinierte Prüfungen.
Am 21.02.2015, 11:54 Uhr, schrieb "Aleksey Bykov" [email protected] :

Warum können Sie nicht einfach aufhören, das Null-Literal zu verwenden und alle Orte zu bewachen, an denen?
Nullen können in Ihren Code eindringen? So sind Sie vor null sicher
Referenzausnahme, ohne überall auf null prüfen zu müssen.
Am 20.02.2015 13:41 Uhr, "electricessence" [email protected]
schrieb:

Werde mich hier einfach wieder melden. Ich bin überrascht, dass wir uns nicht wirklich gelöst haben
Dieses hier.

Ich sage, es gibt hier einen Mehrwert. Eine, die nur beim Kompilieren anwendbar ist
Zeit.
Anstatt mehr Code schreiben zu müssen, um einen Wert auf Null zu überprüfen, können Sie
zu
Deklarieren eines Werts als nicht nullablesbar kann Zeit und Codierung sparen. Wenn ich
beschreiben a
Geben Sie als 'non-nullable' ein, dann muss es initialisiert werden und möglicherweise
behauptete
als ungleich null, bevor sie an eine andere Funktion weitergegeben wird, die erwartet
nicht null.

Wie ich oben sagte, könnte dies vielleicht einfach durch einen Code-Vertrag gelöst werden
Implementierung.

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

.

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

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

Sie übersehen den Punkt, wir verwenden Nullen, um undefinierte Prüfungen zu vermeiden.
Und da unsere Anwendung keinen einheitlichen Status hat, tun wir dies, und
oft müssen Dinge null / undefiniert sein, anstatt ein Objekt zu haben
das darstellen.
Am 21.02.2015 12:20 schrieb "Aleksey Bykov" [email protected] :

Mein Punkt, dass diese Überprüfungen unnötig sind (wenn das Null-Schlüsselwort gesperrt ist
und alle anderen Orte, an denen es eindringen kann, werden bewacht).

Wieso den? 1. Der Code ohne Checks läuft deutlich schneller als Code
mit Prüfungen auf Nullen. 2. Es enthält weniger Wenns und ist daher besser lesbar
und wartbar.
Am 20. Februar 2015, 19:57 Uhr, schrieb "Griffork" [email protected] :

Nullprüfungen sind wesentlich schneller als undefinierte Prüfungen.
Am 21.02.2015, 11:54 Uhr, "Aleksey Bykov" [email protected]
schrieb:

Warum können Sie nicht einfach aufhören, das Null-Literal zu verwenden und alle Orte zu bewachen?
wo
Nullen können in Ihren Code eindringen? So sind Sie vor null sicher
Referenzausnahme, ohne überall auf null prüfen zu müssen.
Am 20.02.2015 13:41 Uhr, "electricessence" [email protected]
schrieb:

Werde mich hier einfach wieder melden. Ich bin überrascht, dass wir es nicht wirklich getan haben
aufgelöst
Dieses hier.

Ich sage, es gibt hier einen Mehrwert. Eine, die nur anwendbar ist bei
kompilieren
Zeit.
Anstatt mehr Code schreiben zu müssen, um einen Wert auf Null zu überprüfen, ist es
fähig
zu
Deklarieren eines Werts als nicht nullablesbar kann Zeit und Codierung sparen. Wenn ich
beschreiben a
Geben Sie als 'non-nullable' ein, dann muss es initialisiert werden und möglicherweise
behauptete
als ungleich null, bevor sie an eine andere Funktion weitergegeben wird, die erwartet
nicht null.

Wie ich oben sagte, könnte dies vielleicht einfach durch einen Code gelöst werden
Verträge
Implementierung.

Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
<

https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

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

.

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


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

Dir fehlen auch meine Punkte. Um Ihren Status konsistent zu halten und fehlende Werte darstellen zu können, müssen Sie keine Nullen oder undefined verwenden. Es gibt andere Möglichkeiten, insbesondere jetzt mit Unterstützung von Unionstypen:

class Nothing { public 'i am nothing': Nothing; }
class Something { 
    constructor(
    public name: string,
    public value: number
   ) { }
}
var nothing = new Nothing();
var whoami = Math.random() > 0.5 ? new Something('meh', 66) : nothing;

if (whoami instanceof Nothing) {
    console.log('i am a null killer');
} else if (whoami instanceof Something) {
    console.log(whoami.name + ': ' + whoami.value);
}

Wir haben also einfach einen fehlenden Wert codiert, ohne eine Null oder undefiniert zu verwenden. Beachten Sie auch, dass wir dies zu 100% explizit tun. Dank Type Guards kann man vor dem Lesen eines Wertes keine Prüfung verpassen.

Wie cool ist das?

Glauben Sie, dass eine Instanz von check schneller ist als eine Nullprüfung, für eine
Anwendung, die Hunderte davon pro Sekunde tun muss?
Lassen Sie es mich so sagen: Wir mussten Aliase für Funktionen erstellen, weil
es ist schneller, '.' nicht zu verwenden. Accessoire.
Am 21.02.2015 12:58 schrieb "Aleksey Bykov" [email protected] :

Dir fehlen auch meine Punkte. Um Ihren Zustand konsistent zu halten und
in der Lage sein, fehlende Werte darzustellen, Sie müssen keine Nullen verwenden oder
nicht definiert. Es gibt andere Möglichkeiten, insbesondere jetzt mit der Unterstützung von Unionstypen
Erwägen:

Klasse Nichts { public 'ich bin nichts': Nichts; }
Klasse etwas {
Konstrukteur(
öffentlicher Name: Zeichenfolge,
öffentlicher Wert: Zahl
) { }
}
var nichts = neues Nichts();
var whoami = Math.random() > 0.5 ? neues Etwas('meh', 66) : nichts;

if (whoami instanceof Nothing) {
// whoami ist nichts
console.log('Ich bin ein Null-Killer');
} else if (whoami instanceof Something) {
// whoami ist eine Instanz von Etwas
console.log(whoami.name + ': ' + whoami.value);
}

Ich habe also gerade einen fehlenden Wert codiert, ohne eine Null oder undefiniert zu verwenden.
Beachten Sie auch, dass wir dies _100% explizit_ tun. Danke an Typ
Wachen, es gibt keine Möglichkeit, eine Prüfung zu verpassen, bevor ein Wert gelesen wird.

Wie cool ist das?


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

@aleksey-bykov Beachten Sie, dass ich diesen Punkt bereits angesprochen habe: Die Notwendigkeit, Typdefinitionen für vorhandene Bibliotheken zu modellieren, die bereits Nullwerte verwenden, ist nicht gelöst. In diesem Fall können Sie nicht einfach auf Nullwerte "aufhören".

Außerdem berührt das nicht einmal Probleme mit nicht initialisierten Variablen, die undefined . In Haskell existiert das Problem nicht einmal, da Sie einen Wert nicht "nicht initialisiert" belassen können.

@Griffork , ich glaube nicht, ich
http://jsperf.com/nullable-vs-null-vs-undefined-vs-instanceof
Ich denke jedoch, dass Sie ein Gleichgewicht zwischen Sicherheit und Leistung finden müssen. Dann müssen Sie Ihre Leistung sorgfältig messen, bevor Sie versuchen, sie zu optimieren. Es besteht die Möglichkeit, dass Sie etwas reparieren, das nicht defekt ist, und Ihre Nullprüfungen, über die Sie so besorgt sind, könnten weniger als 2% der Gesamtleistung ausmachen. Wenn ja, wenn Sie es doppelt so schnell machen, gewinnen Sie nur 1%.

@spion , alles in allem ist es angesichts der aktuellen Situation und der Probleme, die Nicht-Nullables mit sich bringen, ein viel schönerer Weg, als weiterhin Nullen in Ihrem Code zu verwenden

Unsere Codebasis besteht aus über 800 TypeScript-Dateien und über 120000 Codezeilen
und wir brauchten nie Nullen oder Undefinierte, wenn es um die Geschäftsmodellierung ging
Domänen-Entitäten. Und obwohl wir Nullen für DOM-Manipulationen verwenden mussten,
all diese Stellen sind sorgfältig isoliert, damit Nullen keine Möglichkeit haben, auszutreten
in. Ich stimme Ihrem Argument nicht zu, dass Nullen benötigt werden könnten, es gibt
produktionsreife Sprachen ohne Nullen (Haskell) oder mit Null
verboten (F#).
Am 22. Februar 2015 um 09:31 schrieb "Jon" [email protected] :

@aleksey-bykov https://github.com/aleksey-bykov Aus der Praxis
Perspektive können Sie null nicht ignorieren. Hier sind einige Hintergrundinformationen, warum Sie
brauche sowieso einen nullable-Typ:
https://www.google.com/patents/US7627594?ei=P4XoVPCaEIzjsATm4YGoBQ&ved=0CFsQ6AEwCTge

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

Ja, und das Wichtigste, was Sie hier verstehen können, ist, dass wir KEINE Business-Domain-Entitäten modellieren, wir SIND HÖCHST zeitkritisch und unsere Codebasis ist nicht viel kleiner als Ihre.

Für Ihren Zweck brauchen Sie keine Nullen, was in Ordnung ist. Sie können Typescript ohne sie weiter verwenden.
Für unseren Zweck brauchen wir Nullen, und wir können es uns nicht leisten, dies nicht zu tun.

Oh, habe gerade deinen anderen Kommentar gesehen, sorry.
Wenn Sie in diesem Thread etwas weiter vorne gelesen haben, werden Sie feststellen, dass ich (im Wesentlichen) gesagt habe, dass wir immer Nullen verwendet haben und wir selten Probleme damit hatten, dass sie in andere Code-Teile eindringen.

Wenn Sie oben auch gelesen haben, deckt der nicht nullbare Vorschlag auch undefined ab.

Null und undefinierte Variablen sehen hinsichtlich ihrer Geschwindigkeit ähnlich aus, aber sobald sie auf einem Objekt (insbesondere einem mit einer Prototypkette) vorhanden sind, ist die Geschichte ganz anders, also verwenden wir stattdessen Nullen.

Auch hier lohnt es sich wahrscheinlich nicht, sich Sorgen zu machen, es sei denn (wie wir), Sie führen 10.000 Überprüfungen in <10 ms durch.

(Und ja, wir haben uns tatsächlich geändert, weil wir die Leistung unserer Anwendung verfolgt und einen Engpass gefunden haben.)

Dazu möchte ich eine Diskussion anregen. Erst gestern habe ich im Build-Video

Im Grunde ist Analyzer eine Bibliothek, die zusätzliche Syntaxprüfungen, Tag-Zeilen als Fehler/Warnungen durchführen kann und vom Sprachdienst interpretiert wird. Dies würde es effektiv ermöglichen, Nullprüfungen mit einer externen Bibliothek zu erzwingen.

Tatsächlich sehe ich, dass die Analyzer in TypeScript auch für andere Dinge wirklich großartig wären. Es gibt wirklich seltsame Bibliotheken, mehr als C#.

Für alle, die dieses Problem verfolgen : Ich monapt , mit der Sie die NULL-Zulässigkeit einer Variablen deklarieren können. Es enthält Typescript-Definitionen, sodass alles zur Kompilierzeit überprüft wird.

Rückmeldung willkommen!

Habe mir gerade den ganzen Thread durchgelesen.

Ich denke, der Vorschlag sollte die Begriffe non-voidable und voidable , da void in der TS-Spezifikation ein Typ für die Werte null und undefined ist . (Ich gehe davon aus, dass der Vorschlag auch undefinierte Werte abdeckt :smile: )

Ich stimme auch zu, dass Zugriff undefiniert oder Null-Ausnahme die Wurzel allen Übels bei der Programmierung ist. Das Hinzufügen dieser Funktion wird den Leuten unzählige Stunden sparen.

Bitte fügen Sie dies hinzu! Und es wäre am besten, wenn dies ein Feature würde, dem ich ein Flag übergeben könnte, um es zum Standard zu machen. Es würde vielen Leuten eine Menge Debugging-Hölle ersparen.

Ich kann zwar zustimmen, dass ADTs anstelle von NULL-fähigen Typen verwendet werden können; Wie können wir sicher sein, dass der von uns übergebene ADT selbst nicht null ist? Jeder Typ in TS kann null sein, einschließlich ADTs, oder? Ich sehe darin einen großen Fehler bei der Betrachtung von ADTs als Lösung für das Problem nicht-nullierbarer (nicht-voidabler) Typen. ADTs funktionieren nur so gut wie in Sprachen, die nicht-nullbare Typen zulassen (oder verbieten).

Ein weiteres wichtiges Thema sind Bibliotheksdefinitionen. Ich habe vielleicht eine Bibliothek, die einen String als Argument erwartet, aber der TS-Compiler kann check eingeben und null an die Funktion übergeben! Das stimmt einfach nicht...

Ich sehe es als großen Fehler an, über ADTs als Lösung des Problems nachzudenken...

  1. Erhalten Sie ADTs über ein Designmuster (aufgrund des Fehlens von nativen
    Unterstützung von TypeScript)
  2. das Schlüsselwort null verbieten
  3. Bewachen Sie alle Stellen, an denen Nullen von außen in Ihren Code eindringen können
  4. genieße das null Problem für immer

In unserem Projekt haben wir die Schritte 1, 2 und 3 geschafft. Rate mal, was wir tun
jetzt.

@aleksey-bykov

Wie geht (2) mit undefined ? Es kann in einer Vielzahl von Situationen auftreten, in denen das Schlüsselwort undefined : nicht initialisierte Klassenmitglieder, optionale Felder, fehlende Rückgabeanweisungen in bedingten Verzweigungen ...

Vergleichen Typoskript mit flowtype . Zumindest Flow kümmert sich um die meisten Fälle.

Auch wenn Sie bereit sind zu sagen "Vergessen Sie die gängigen Redewendungen in der Sprache. Ist mir egal, ich werde mich vom Rest der Welt isolieren", dann sind Sie wahrscheinlich besser dran, purescript zu verwenden .

Es kann in einer Vielzahl von Situationen auftreten, in denen das undefinierte Schlüsselwort nicht beteiligt ist...

  • nicht initialisierte Klassenmitglieder - Klassen verboten
  • optionale Felder - optionale Felder/Parameter verboten
  • fehlende Rückgabeanweisungen - eine benutzerdefinierte Tslint-Regel, die sicherstellt, dass alle Ausführungspfade zurückkehren

besser dran mit purescript

  • Ich wünschte, aber der Code, den er generiert, betrifft die Leistung am wenigsten

@aleksey-bykov es ist witzig, dass Sie die Leistung erwähnen, da das Ersetzen von Nullable-Typen durch ADTs einen erheblichen CPU- und Speicher-Overhead verursacht, verglichen mit dem Erzwingen von Nullprüfungen (wie Flowtype). Das gleiche gilt für die Nichtverwendung von Klassen (kann unerschwinglich teuer sein, wenn Sie viele Objekte mit vielen Methoden instanziieren)

Sie erwähnen, optionale Felder aufzugeben, aber sie werden von so vielen JS-Bibliotheken verwendet. Was haben Sie getan, um mit diesen Bibliotheken umzugehen?

Sie erwähnen auch, dass Sie keine Klassen verwenden. Ich nehme an, Sie vermeiden auch Bibliotheken, die auf Klassen angewiesen sind (oder sich verlassen werden) (wie zB React)?

Auf jeden Fall sehe ich keinen Grund, auf so viele Funktionen zu verzichten, wenn eine absolut vernünftige Lösung (die tatsächlich zu der zugrunde liegenden untypisierten Sprache passt) am wahrscheinlichsten implementiert werden kann.

Es ist komisch, dass Sie denken, dass der einzige Zweck von ADTs darin besteht, einen fehlenden Wert darzustellen, der sonst mit null codiert worden wäre

Ich würde eher sagen, dass das Wegfallen des "Null"-Problems (und auch des Unterrichts) ein Nebenprodukt all der guten Dinge ist, die uns ADTs bringen

Um Ihre Neugier zu befriedigen, verwenden wir für leistungskritische Teile wie enge Schleifen und große Datenstrukturen die sogenannte Nullable<a> Abstraktion, indem wir TypeScript vorgaukeln, dass es sich um einen umschlossenen Wert handelt, aber tatsächlich (zur Laufzeit) ist ein solcher Wert nur ein Primitiv, das Nullen annehmen darf

Lass es mich wissen, wenn du die Details wissen möchtest

interface Nullable<a> {
    'a nullable': Nullable<a>;
    'uses a': a;
}
/*  the following `toNullable` function is just for illustration, we don't use it in our code,
    because there are no values capable of holding naked null roaming around,
    instead we just alter the definition of all unsafe external interfaces:
    // before
    interface Array<T> {
       find(callback: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T;
    }
    // after
    interface Array<a> {
       find(callback: (value: a, index: number, array: a[]) => boolean, thisArg: any): Nullable<a>;
    }
*/
function toNullable<a>(value: a) : Nullable<a> {
    return <any>value;
}
function toValueOrDefault<a>(value: Nullable<a>, defaultValue: a)  : a {
    return value != null ? <any>value : defaultValue;
}

Warum eine rekursive Typstruktur verwenden? 'a nullable': a sollte ausreichen, oder?

Ich nehme an, Sie haben auch callFunctionWithNullableResult<T,U>(f: (T) => U, arg:T):Nullable<U> (einschließlich aller anderen Überladungen für Funktionen mit unterschiedlicher Stelligkeit), um mit allen externen Funktionen umzugehen, die Nullen zurückgeben?

Und wenn das Schlüsselwort null durch einen Linter verboten ist, wie bekommt man dann ein " Nothing " gespeichert in einem Nullable? spezialisieren Sie toNullable(null) ?

Ich weiß, was algebraische Datentypen sind. Können Sie mir eine Instanz geben (abgesehen von Vollständigkeitsprüfungen und der Klobigkeit), in der ADTs (oder besser Summentypen) + Musterabgleich etwas tun können, was die Unions des Typoskripts + benutzerdefinierte Typwächter nicht können?

Warum eine rekursive Typstruktur verwenden? 'a nullable': a sollte ausreichen, nicht wahr?

Nun, in diesem Beispiel ist es genug, aber wenn man es so macht, wird der Zweck eines solchen Feldes überladen, was zwei verschiedenen Fällen dient:

Ich persönlich hasse es, überladen zu sein, was bedeutet, dass ich mit 2 separaten Pseudofeldern arbeite, von denen jedes ein separates Problem löst

Ich nehme an, Sie haben auch callFunctionWithNullableResult(f: T -> U, arg:T) => Nullable

nein, wie gesagt, wir ändern die Definitionsdateien (*.d.ts), indem wir alles, wovon wir wissen, dass es im externen Code null sein kann, durch einen Nullable "Wrapper" ersetzen, siehe das Beispiel im Kommentar in meiner vorherigen Nachricht

Wie bekommt man ein "Nichts", das in einem Nullable gespeichert ist?

Wir speichern keine Nullen in Nullables, Nullables kommen vom Server (über den wir nur begrenzte Kontrolle haben) oder von den externen APIs, sobald sie in unserem Code sind, wird sie in Nullable verpackt, aber unser Code ist nicht in der Lage, zu produzieren eine Nullable nach Belieben, wir können nur eine gegebene Nullable in eine andere Nullable umwandeln, aber keine aus dem Nichts erschaffen

Die Unions von typescript + benutzerdefinierte Typwächter können dies nicht tun

Ha! Ich bin überhaupt kein Fan von Gewerkschaftstypen und kann stundenlang darüber reden, was sie zu einer schlechten Idee macht

zurück zu deiner Frage, gib mir ein wiederverwendbares Äquivalent von Either a b mit Unions und Type Guards
(ein Hinweis: https://github.com/Microsoft/TypeScript/issues/2264)

Die Typen in Ihrem Beispiel sind nicht nominaler, nur weil sie rekursiv sind - sie bleiben "pseudo-nominal".

Um Ihre Frage zu beantworten: Sie implementieren kein generisches Entweder, gerade weil Typen nicht nominal sind. Sie verwenden einfach

type MyType = A | B

und geben Sie dann einen Wächter für verwenden A und B ... Als Bonus Dies funktioniert mit jeder vorhandenen JS - Bibliothek zu: zB die idiomatische Möglichkeit zu überprüfen , für Thenable ist , wenn es ist eine then Methode, die an das Objekt angehängt ist. Funktioniert da ein Typenwächter? Ganz bestimmt.

Ha! Das ist ein verdammt guter Fang von dir. Nun, in den letzten Versionen des Compilers muss sich etwas geändert haben. Hier ist das kugelsichere Nominal:

enum GottaBeNonimalBrand {}
interface GottaBeNonimal {
     branded: GottaBeNonimalBrand;
}
function toGottaBeNominal() : GottaBeNominal {
   return <any> {};
}

Warte kurz! Du hast betrogen ! Der gute alte Weg funktioniert immer noch wie ein Zauber.

Um Ihre Frage zu beantworten: Sie implementieren kein generisches Entweder, gerade weil Typen nicht nominal sind. Sie verwenden einfach...

das ist nett was du sagst, wie ist es aber Either ? das authentische Either ist GENERIC und es gibt ungefähr 30 Standard-Komfortfunktionen, jetzt sagen Sie, ich muss aufgelöst (nominal) gehen und bis zu 30 x number_of_combinations_of_any_2_types_in_my_app dieser Funktionen für alle möglichen Paare haben, die ich könnte begegnen?

Hoffentlich wird dieser Thread noch von Leuten aus dem TS-Team überwacht. Ich möchte versuchen, es neu zu starten!
Mir scheint, dass Nicht-Null-Typen stecken geblieben sind, als klar wurde, dass es unmöglich war, dies auf eine bruchsichere Weise einzuführen. Das C#-Team denkt über Referenztypen nach, die NULL-Werte zulassen, und steht natürlich vor den gleichen Problemen, insbesondere der Kompatibilität. Ich mag die Ideen, die sie im Moment erforschen, und ich frage mich, ob sie auf TS anwendbar wären.

C# vnext Brainstorming (kurz)

Grundsätzlich haben Sie in C# Nicht-Null-Werttypen wie int . Dann haben wir in C# 2.0 die Nullable-Werttypen erhalten, z. B. int? . Referenzen waren schon immer nullfähig, aber sie denken darüber nach, dies zu ändern und dieselbe Syntax anzuwenden: string ist nie null, string? kann es sein.

Das große Problem ist natürlich, dass dies die Bedeutung von string ändert und Code bricht. Wo es in Ordnung war, null einem string zuzuweisen/zurückzugeben, ist es jetzt ein Fehler. Das C#-Team denkt daran, diese Fehler zu "deaktivieren", es sei denn, Sie stimmen zu. Das Punktieren in string? ist immer ein Fehler, da die Syntax neu ist und vorher nicht erlaubt war (daher ist es keine Breaking Change).
Das andere Problem liegt bei anderen Bibliotheken usw. Um das zu beheben, möchten sie das Opt-in-Flag pro Datei oder Assembly erstellen. Externer Code, der sich für das strengere Typsystem entscheidet, profitiert also von den Vorteilen, und alter Legacy-Code und -Bibliotheken funktionieren weiterhin.

Wie könnte das auf TS übertragen werden?

  1. Wir führen ein Opt-in-Flag für Nicht-Null-Typen ein. Es kann global (an den Compiler übergeben) oder pro Datei sein. Es sollte immer pro Datei für .d.ts .
  2. Grundtypen sind nicht nullable, zB number . Es gibt einen neuen null Typ.
  3. number? ist einfach eine Abkürzung für number | null .
  4. Die Funktion führt neue Fehler für Nullable-Typen ein, z. B. (x: string?) => x.length ohne Typschutz.
  5. Die Funktion führt neue Fehler für Typen ein, die keine NULL-Werte zulassen, wie z. B. let x: string = null; assign zuweisen
  6. Beim Manipulieren eines Nicht-Null-Typs, der außerhalb des Opt-In-Bereichs deklariert ist, werden die unter 5. gemeldeten Fehler ignoriert. _Das ist etwas anders als zu sagen, dass string immer noch als string? gilt._

Was ist das gut?

Wenn ich neuen Code schreibe, kann ich mich anmelden und vollständige statische Nullprüfungen in meinem Code und in aktualisierten Bibliotheksdefinitionen erhalten. Ich kann weiterhin fehlerfreie Legacy-Bibliotheksdefinitionen verwenden.

Wenn ich alten Code verwende, kann ich mich für neue Dateien anmelden. Ich kann sowieso string? deklarieren und bekomme Fehler beim Zugriff auf Mitglieder ohne eine ordnungsgemäße Nullprüfung. Ich erhalte auch Vorteile und strenge Prüfungen für Bibliotheken, deren Definitionen aktualisiert wurden. Sobald sich ein .d.ts angemeldet hat, wird die Übergabe von null an eine als length(x: string): number definierte Funktion zu einem Fehler.
_(Hinweis: Die Übergabe string string? ist ebenfalls ein Fehler, die Übergabe von string aus dem Opt-out-Code jedoch nicht.)_

Solange ich mich nicht anmelde, gibt es keine Breaking Change.

Was denkst du?

Natürlich gibt es einiges zu beachten, wie das Feature in der Praxis funktionieren würde. Aber ist ein solches Opt-in-Schema etwas, das das TS-Team überhaupt in Betracht ziehen könnte?

Es ist erstaunlich, wie die Leute ein schnelleres Pferd anstelle eines Autos wollen.

Keine Notwendigkeit, herablassend zu sein. Ich habe Ihre Kommentare zu ADT gelesen und glaube nicht, dass sie das sind, was ich brauche. Wir werden das vielleicht diskutieren, wenn Sie möchten, aber in einer neuen Ausgabe zum Thema "ADT-Unterstützung in TS". Sie können dort schreiben, warum sie großartig sind, warum wir sie brauchen und wie sie den Bedarf an nicht nullbaren Typen verringern. In dieser Ausgabe geht es um Typen, die keine NULL-Werte zulassen, und Sie übernehmen das Thema wirklich.

worüber Sie sprechen, wird Konstantenpropagation genannt und sollte, falls implementiert, nicht nur durch 2 zufällige Konstanten begrenzt werden, die zufällig null und undefined . Dasselbe kann mit Saiten, Zahlen und was auch immer gemacht werden und so macht es Sinn, sonst wäre es nur um ein paar alte Gewohnheiten zu pflegen, die manche Leute nicht ganz so recht haben wollen

@aleksey-bykov und natürlich implementieren Sie dann, um bessere Typeinschränkungen zu erhalten, höherwertige Typen, und um abgeleitete Instanzen zu erhalten, implementieren Sie Typklassen und so weiter und so weiter. Wie passt das mit dem Designziel „Align with ES6+“ von TypeScript zusammen?

Und es ist immer noch alles nutzlos, um das ursprüngliche Problem dieser Ausgabe zu lösen. Es ist einfach, Maybe (und Entweder) in TS mit generischen Klassen zu implementieren, sogar heute (ohne Unionstypen). Es wird ungefähr so ​​nützlich sein, wie das Hinzufügen von Optional zu Java war, das heißt: kaum. Weil jemand einfach null irgendwo zuweist oder optionale Eigenschaften benötigt, und die Sprache selbst wird sich nicht beschweren, aber die Dinge werden zur Laufzeit explodieren.

Es ist mir unklar, warum Sie darauf bestehen, die Sprache zu missbrauchen, um sie dazu zu bringen, sich auf eine Weise zu verbiegen, für die sie nie gedacht war. Wenn Sie eine funktionale Sprache mit algebraischen Datentypen und keinen Nullwerten wünschen, verwenden Sie einfach PureScript. Es ist eine ausgezeichnete, gut gestaltete Sprache mit Funktionen, die kohärent zusammenpassen, im Laufe der Jahre in Haskell kampferprobt ohne die angesammelten Warzen von Haskell (prinzipielle Typenklassenhierarchie, echte Datensätze mit Zeilenpolymorphismus ...). Mir scheint, dass es viel einfacher ist, mit einer Sprache zu beginnen, die Ihren Prinzipien entspricht und dann für die Leistung optimiert werden muss, anstatt mit etwas zu beginnen, das nicht (und nie dazu bestimmt war) und dann zu drehen, zu wenden und einzuschränken, um es zu erreichen arbeite deinen Weg.

Was ist Ihr Leistungsproblem mit PureScript? Sind es die Verschlüsse, die durch das allgegenwärtige Curry entstehen?

@jods4 bedeutet dies, dass alle derzeit vorhandenen .d.ts-Dateien mit einem Out-of-Work-Flag aktualisiert werden müssen oder dass die projektweite Einstellung .d.ts (und eine .d.ts ohne Flagge wird immer als "alt" angenommen.
Wie verständlich ist das (wird es Probleme für Leute verursachen, die an mehreren Projekten arbeiten, oder Verwirrung beim Lesen von .d.ts-Dateien)?
Wie wird die Schnittstelle zwischen neuem und altem (Bibliotheks-)Code aussehen? Wird es mit einer Menge (in einigen Fällen) unnötigen Checks und Casts übersät sein (wahrscheinlich keine schlechte Sache, aber es könnte Anfänger von der Sprache abschrecken)?
Am meisten mag ich (jetzt) ​​die Idee von Nicht-Nullable-Typen, und der Vorschlag von @jods4 und der ! Vorschlag ist die einzige Möglichkeit, wie ich Nicht-Nullable-Typen funktionieren sehe (ohne Berücksichtigung von Adts).

Es ist vielleicht in diesem Thread verloren gegangen, aber welches Problem löst das? Das ist etwas, was mich angedeutet hat. Die Argumente, die ich kürzlich in diesem Thread gesehen habe, sind "andere Sprachen haben es". Es fällt mir auch schwer zu erkennen, was dies nützlich ist und keine Machenschaften mit TypeScript erfordert. Obwohl ich verstehen kann, dass null und undefined Werte unerwünscht sein können, neige ich dazu, dies als eine Logik höherer Ordnung zu sehen, die ich in meinen Code einbauen möchte, anstatt zu erwarten, dass die zugrunde liegende Sprache damit umgeht .

Der andere kleine Fehler ist, dass jeder undefined als Konstante oder als Schlüsselwort behandelt. Technisch ist es das auch nicht. Im globalen Kontext gibt es eine Variable/Eigenschaft undefined vom primitiven Typ undefined . null ist ein Literal (das zufällig auch vom primitiven Typ null , obwohl es ein seit langem bestehender Fehler war, dass es implementiert wurde, um typeof "object" ). In ECMAScript sind dies zwei Dinge, die ziemlich unterschiedlich sind.

@kitsonk
Das Problem ist, dass alle Typen null sein können und alle Typen undefiniert sein können, aber nicht alle Operationen, die mit einem bestimmten Typ arbeiten, funktionieren nicht mit null oder undefinierten Werten (zB Division), was zu Fehlern führt.
Was sie wollen, ist die Möglichkeit, einen Typ als "ist eine Zahl und ist niemals null oder undefiniert" anzugeben, damit sie sicherstellen können, dass bei der Verwendung einer Funktion, die die Möglichkeit hat, null oder undefiniert einzuführen, Programmierer sich vor ungültigen Werten schützen können.
Es gibt zwei Hauptargumente, um einen Wert als "ist dieser Typ, kann aber nicht null oder undefiniert sein" angeben:
A) Es ist für alle Programmierer, die den Code verwenden, offensichtlich, dass dieser Wert niemals null oder undefiniert sein darf.
B) der Compiler hilft, fehlende Typwächter zu erkennen.

es gibt eine Variable/Eigenschaft undefined, die vom primitiven Typ undefined ist. null ist ein Literal (das zufällig auch vom primitiven Typ ist null

@kitsonk das ist ein großartiges Argument für dieses Problem; null ist kein number , es ist nicht mehr ein Person als ein Car . Es ist ein eigener Typ, und in diesem Thema geht es darum, dass Typoskript diese Unterscheidung treffen kann.

@kitsonk Ich denke, einer der häufigsten Fehler sind https://github.com/Microsoft/TypeScript/issues/3692.

Danke für die Klarstellung. Vielleicht übersehe ich auch hier wieder etwas, nachdem ich das nochmal durchgesehen habe... Da es in TypeScript bereits ein "Optionalität"-Argument gibt, warum sollte das Folgende als Vorschlag nicht funktionieren?

interface Mixed {
    optional?: string;
    notOptional: string;
    nonNullable!: string;
}

function mixed(notOptional: string, notNullable!: string, optional?: string): void {}

Obwohl einige der vorgeschlagenen Lösungen eine größere Funktionalität bieten, vermute ich, dass der Typschutz dagegen zu einer Herausforderung wird, da dies (obwohl ich das Innenleben von TypeScript nicht gut kenne) wie eine Erweiterung dessen zu sein scheint, was bereits geprüft wird.

Da es in TypeScript bereits ein Argument "Optionalität" gibt, warum sollte Folgendes nicht als Vorschlag funktionieren?

Nur um zu verdeutlichen, dass die optionalen Optionen in TS nur während der Initialisierung optional sind. Null und undefined ist weiterhin allen Typen zuweisbar.

Dies sind einige nicht intuitive Fälle von Optionals

interface A {
   a?: string;
}
let a: A = {} // ok as expected

interface B {
   b: string;
}
let b1: B = { b: undefined } //ok, but unintuitive
let b2: B = { b: null } // ok, but unintuitive
let b3: B = {} // error as expected

Oder einfach gesagt: optional in TS bedeutet nicht initialisierbar. Dieser Vorschlag besagt jedoch, dass undefined und null einem nicht voidablen (nicht null, nicht undefinierten) Typ zugewiesen werden können.

Das ? wird durch den Eigenschafts-/Argumentnamen platziert, weil es sich auf die _Existenz_ dieses Namens bezieht oder nicht. @tinganho hat es gut erklärt. Erweiterung seines Beispiels:

function foo(arg: string, another?: string) {
  return arguments.length;
}

var a: A = {};
a.hasOwnProperty('a') // false
foo('yo') // 1, because the second argument is _non-existent_.
foo(null) // this is also ok, but unintuitive

! ungleich Null hat nichts mit der _Existenz_ eines Namens zu tun. Es hängt mit dem Typ zusammen, weshalb es nach dem Typ platziert werden sollte. Der Typ ist nicht-nullierbar.

interface C {
  c: !string;
}

let c1: C = { }; // error, as expected
let c2: C = { c: null }; // error, finally!
let c3: C = { c: 'str' }; // ok! :)

function bar(arg: !string) {
  return arg.length;
}

bar(null) // type error
bar('foo') // 3

@Griffork Vorhandenes .d.ts würde gut funktionieren und kann reibungslos aktualisiert werden, um strengere Definitionen zu unterstützen.
Standardmäßig ist .d.ts nicht aktiv. Wenn ein Bibliotheksautor seine Bibliothek mit getreuen Nicht-Null-Definitionen aktualisiert, zeigt er dies an, indem er ein Flag am Anfang der Datei setzt.
In beiden Fällen funktioniert einfach alles, ohne dass der Verbraucher nachdenken / konfigurieren muss.

@kitsonk Das Problem, das dadurch gelöst wird, besteht darin, Fehler zu reduzieren, da die Leute davon ausgehen, dass etwas nicht null ist, wenn es sein könnte, oder Nullen an Funktionen übergeben, die es nicht unterstützen. Wenn Sie mit vielen Leuten an großen Projekten arbeiten, können Sie Funktionen nutzen, die Sie nicht geschrieben haben. Oder Sie verwenden Bibliotheken von Drittanbietern, die Sie nicht gut kennen. Wenn Sie Folgendes tun: let x = array.sum(x => x.value) , können Sie dann davon ausgehen, dass x niemals null ist? Vielleicht ist es 0, wenn das Array leer ist? Woher weißt du das? Darf Ihre Sammlung x.value === null ? Wird das von der Funktion sum() oder handelt es sich um einen Fehler?
Mit dieser Funktion werden diese Fälle statisch geprüft und das Übergeben eines möglicherweise Null-Werts an eine Funktion, die dies nicht unterstützt, oder das Verbrauchen eines möglicherweise Null-Werts ohne Prüfung als Fehler gemeldet.
Grundsätzlich kann es in einem vollständig typisierten Programm keine "NullReferenceException" mehr geben (abzüglich der Eckfälle, die sich auf undefined beziehen, leider).

Die Diskussion über optionale Parameter ist unabhängig. Das Zulassen von null oder nicht hängt mit dem Typsystem zusammen, nicht mit den Variablen- / Parameterdeklarationen, und es lässt sich gut in Typwächter, Unions- und Schnitttypen usw. integrieren.

type _void_ hat keine Werte, aber sowohl null als auch undefined können ihm zugewiesen werden

hier ist eine gute Zusammenfassung: https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71942237

@jbondc Ich glaube, ich habe die Spezifikation kürzlich überprüft. void ist ein Überbegriff für null und undefined . Ich weiß nicht über void 0 , aber ich denke, void hat es auch unter seinem Dach.

Leitet any . null und undefined sind jedoch weiterhin allen Typen zuweisbar.

@jbondc wenn es sich bei der Frage um ein potentiel-

function returnsNull() {
  return null;
}

sollte den Typ null ableiten.

Es gibt viele Eckfälle um undefined und um ehrlich zu sein, bin ich mir (noch?) nicht sicher, wie ich das am besten angehen kann. Aber bevor ich lange darüber nachdenke, würde ich gerne wissen, ob das TS-Team mit einer Opt-in-Strategie einverstanden ist.

Als ich das letzte Mal an dieser Diskussion teilnahm, endete sie mit: "Wir werden keine Breaking Changes vornehmen und wir wollen die Bedeutung des Quellcodes nicht basierend auf Ambient-Flags/Optionen ändern". Die oben beschriebene Idee ist keine bahnbrechende Änderung und relativ gut in Bezug auf die Quelleninterpretation, wenn auch nicht 100%. Diese Grauzone ist der Grund, warum ich frage, was das TS-Team denkt.

@jods4 Ich würde das abgeleitete nullable any bevorzugen, das die vorhandene Syntax beibehalten würde. Und eine Opt-in-Strategie wäre vorzuziehen. [1]

Ich würde eine explizite Syntax für Typen ohne NULL-Werte bevorzugen. Bei Objekten würde es ein paar zu viele Dinge auf einmal zerstören. Außerdem sollten optionale Argumente aus offensichtlichen Gründen immer nullfähig sein (und es sollte unmöglich sein, sie zu ändern). Standardargumente sollten ihre aktuelle Signatur extern beibehalten, aber innerhalb der Funktion selbst kann das Argument nicht-nullfähig gemacht werden.

Ich finde dies jedoch absurd, da Nullable-Zahlen viele der Optimierungs-Engines unterbrechen, und dies ist kein häufiger Anwendungsfall außerhalb von optionalen / Standardargumenten. Es könnte ein wenig praktikabler/nicht brechender sein, Zahlen, die keine optionalen Argumente sind, standardmäßig über ein Compiler-Flag als nicht-nullfähig zu machen. Dies würde auch eine Syntax erfordern, um Nullable-Typen explizit zu markieren, aber ich sehe dies bereits als wahrscheinliches Ergebnis dieser Diskussion.

[1] Die Einführung eines Flags, um dies zum Standard zu machen, würde aus praktischen Gründen von vornherein nicht gut funktionieren. Sehen Sie sich einfach die Probleme an, auf die die Leute mit --noImplicitAny , die zu einigen Migrationsgefahren, vielen praktischen Problemen beim Testen der Existenz von Eigenschaften, die zu --suppressImplicitAnyIndexErrors , und mehreren fehlerhaften DefinitelyTyped-Definitionen führten.

@impinball

Ich würde das abgeleitete nullable any bevorzugen, das die vorhandene Syntax beibehalten würde. Und eine Opt-in-Strategie wäre vorzuziehen.

Ich habe meine Antwort hier bearbeitet. Wenn ich genauer darüber nachdenke, sehe ich einfach keinen nützlichen Fall, in dem Sie implizit nur den Typ null ableiten können. Zum Beispiel: () => null oder let x = null oder function y() { return null } .

Daher stimme ich zu, weiterhin zu folgern, dass any wahrscheinlich eine gute Sache ist.

  1. Es ist abwärtskompatibel.
  2. All diese Fälle sind sowieso Fehler unter noImplicitAny .
  3. Wenn Sie einen seltsamen Fall haben, in dem Sie dies tun möchten und es für Sie nützlich ist, können Sie den Typ explizit angeben: (): null => null oder let x: null = null oder function y(): null { return null } .

Ich würde eine explizite Syntax für Typen ohne NULL-Werte bevorzugen. Bei Objekten würde es ein paar zu viele Dinge auf einmal zerstören.

Zu sagen, dass string tatsächlich string! | null kann eine andere Option sein. Es sollte beachtet werden, dass dies, wenn Sie alle obigen Diskussionen lesen, zunächst vorgeschlagen wurde, weil man hoffte, dass es mit dem vorhandenen Code kompatibel wäre (ohne die aktuelle Bedeutung von string ändern). Aber die Schlussfolgerung war tatsächlich, dass trotzdem riesige Mengen an Code kaputt gehen würden.

Angesichts der Tatsache, dass die Übernahme eines Typsystems ohne Nullwerte für bestehende Codebasen kein trivialer Wechsel sein wird, würde ich die Option string? Option string! vorziehen, da sie sich sauberer anfühlt. Vor allem, wenn Sie sich den Quellcode des TS-Compilers ansehen, in dem so ziemlich alle internen Typen falsch benannt werden :(

Es könnte ein wenig praktikabler/nicht brechender sein, Zahlen, die keine optionalen Argumente sind, standardmäßig über ein Compiler-Flag als nicht-nullfähig zu machen. Dies würde auch eine Syntax erfordern, um Nullable-Typen explizit zu markieren, aber ich sehe dies bereits als wahrscheinliches Ergebnis dieser Diskussion.

Ich denke, die Dinge werden an dieser Stelle verwirrend. Für mich scheint dies ein gutes Argument zu sein, überall string? . Ich möchte nicht, dass number? und string! verwechselt werden, das würde zu schnell zu unübersichtlich werden.

[1] Die Einführung eines Flags, um dies zum Standard zu machen, würde aus praktischen Gründen von vornherein nicht gut funktionieren.

Ja, es würde ohne Änderungen nicht gut für eine vorhandene Codebasis funktionieren. Aber:

  1. Es wird großartig für neue Codebasen funktionieren. Damit können wir eine bessere Zukunft aufbauen.
  2. Vorhandene Codebasen profitieren von _einigen_ Vorteilen und zusätzlichen Prüfungen, selbst wenn sie sich nie anmelden.
  3. Vorhandene Codebasen können sich pro Datei anmelden, wodurch neuer Code strenger ist und bei Bedarf ein progressiver Übergang von älterem Code möglich ist.

@jods4

Ich denke, die Dinge werden an dieser Stelle verwirrend. Für mich scheint dies ein gutes Argument für die Verwendung von String zu sein? überall, überallhin, allerorts. Ich möchte die Nummer nicht verwechseln sehen? und string!, das würde zu schnell zu unübersichtlich werden.

Ich meinte es nicht in diesem Sinne. Ich meinte, es gibt weniger Anwendungsfälle für nullbare Zahlen als für nullbare Zeichenfolgen. Ich hänge sowieso nicht so an diesem Vorschlag (mir wäre das sowieso egal).

Vorhandene Codebasen können sich pro Datei anmelden, wodurch neuer Code strenger ist und bei Bedarf ein progressiver Übergang von älterem Code möglich ist.

Mit welchen Mitteln? Ich glaube nicht, dass Sie den Compiler derzeit dateiweise konfigurieren können. Ich bin offen dafür, dass mir hier das Gegenteil bewiesen wird.

@impinball

Mit welchen Mitteln? Ich glaube nicht, dass Sie den Compiler derzeit dateiweise konfigurieren können. Ich bin offen dafür, dass mir hier das Gegenteil bewiesen wird.

Vielleicht liege ich falsch. Ich habe sie noch nie verwendet, aber wenn ich mir Testfälle ansehe, sehe ich viele Kommentare wie // <strong i="9">@module</strong> amd . Ich bin immer davon ausgegangen, dass es eine Möglichkeit ist, Optionen aus einer ts-Datei heraus anzugeben. Ich habe es jedoch nie versucht, also liege ich vielleicht völlig falsch! Schauen Sie zum Beispiel hier:
https://github.com/Microsoft/TypeScript/blob/master/tests/cases/conformance/externalModules/amdImportAsPrimaryExpression.ts

Wenn dies keine Möglichkeit ist, dateispezifische Optionen anzugeben, benötigen wir möglicherweise etwas Neues. Es ist erforderlich, diese Option pro Datei anzugeben, _mindestens_ für .d.ts Dateien. Ein speziell formatierter Kommentar oben in der Datei könnte reichen, schließlich haben wir bereits Unterstützung für /// <amd-dependency /> und Co.

EDIT : Ich liege falsch. Das Spelunking des Quellcodes hat mir gezeigt, dass diese Dinge Marker genannt und vom FourSlash-Runner vorab geparst werden. Also nur für Tests, nicht im Compiler. Dafür müsste man sich etwas Neues einfallen lassen.

In einigen Fällen gibt es auch praktische Einschränkungen - für einige Argumente wie ES6-Funktionen ist dies nicht die praktischste, da die Datei vollständig neu analysiert werden müsste. Und IIUC wird die Typprüfung im selben Durchgang wie die AST-Erstellung durchgeführt.

Wenn Sie sich den Parser-Code ansehen, werden die Kommentare /// <amd-dependency /> und /// <amd-module /> gelesen, bevor alles andere in einer Datei geparst wird. Etwas wie /// <non-null-types /> hinzuzufügen wäre machbar. OK, das ist eine schreckliche Namensgebung und ich bin nicht für die Verbreitung willkürlicher "magischer" Optionen, aber das ist nur eine Idee.

@jods4 Ich denke, 'm Pinball sagt, dass IDEs dies ohne große Änderungen an der Funktionsweise der Syntaxhervorhebung nicht unterstützen werden.

Wird es in 2.0 irgendeine Form von nicht-nullbaren Typen geben?
Schließlich bringt Typoskript weniger Dinge in das Javascript, wenn es den Typ nicht garantieren kann. Was können wir nach Überwindung aller Probleme und Überarbeitungen erhalten, die durch Typoskript eingeführt wurden?
Einfach besser IntelliSense? Da muss man nach wie vor bei jedem Funktionsexport in andere Module den Typ manuell oder per Code prüfen.

Gute Nachrichten an alle, TypeScript 1.6 hat genug Ausdruckskraft, um einen fehlenden Wert zu modellieren und sicher zu verfolgen (der ansonsten durch null und undefined Werte kodiert wird). Im Wesentlichen gibt Ihnen das folgende Muster nicht-nullbare Typen:

declare module Nothing { export const enum Brand {} }
interface Nothing { 'a brand': Nothing.Brand }
export type Nullable<a> = a | Nothing;
var nothing : Nothing = null;
export function isNothing(value: Nullable<a>): value is Nothing {
    return value == null;
}
var something = Math.random() > 0.5 ? 'hey!' : nothing;
if (isNothing(something)) {
    // missing value
    // there is no way you can get anything out of it
    // there is also NO WAY to get a null reference exception out of it
    // because it doesn't have any methods or properties that could be examined
    // it is 100% explicit and typesafe to use
} else {
    // value is present, it is 100% GUARANTEED being NON-NULL
    // you just CANT get a null reference exception here either
    console.log(something.toLowerCase());
}

/** turns any unsafe values into safe ones */
export function sanitize<a>(unsafe: a) : Nullable<a> {
    return unsafe;
}

var safe = sanitize(toResultFromExternalCodeYouCannotTrust()); // <-- 100% safe to use

Damit sollte die Anfrage geschlossen werden, da kein Problem mehr besteht. Fall abgeschlossen, Klasse abgewiesen.

@aleksey-bykov

Damit sollte die Anfrage geschlossen werden, da kein Problem mehr besteht.

Das kann doch nicht dein Ernst sein, oder? Wir haben Ihnen mehrmals gesagt, dass ein Muster für nullable algebraische Typen nicht das war, worum es bei diesem Problem ging.

Ich werde _nicht_ jedes einzelne var x: number in meinem Code in ein Nullable<number> oder noch besser NonNullable<x> umschließen. Das ist einfach zu viel Overhead. Ich möchte jedoch wissen, dass das Ausführen von x *= 2 100% sicher ist, wo immer dies in meinem Code passiert.

Ihr Code löst nicht das Problem der Verwendung einer Drittanbieterbibliothek. Ich werde _nicht_ sanitize in _jeder_ Bibliothek von Drittanbietern oder sogar der integrierten DOM-API aufrufen, die ich aufrufe. Außerdem möchte ich nicht isNothing(safe) _überall_ hinzufügen. Ich möchte let squares = [1,2,3].map(x => x*x) aufrufen und zur Kompilierzeit 100% sicher sein, dass squares.length sicher ist. Mit _jeder_ API, die ich verwende.

Ebenso möchte ich Dokumentation für JS-Bibliotheken von Drittanbietern, dass eine Funktion null als Eingabe akzeptiert oder nicht. Ich möchte zur Kompilierzeit wissen, ob $(element).css(null) OK oder ein Fehler ist.

Ich möchte in Teamumgebungen arbeiten, in denen ich nicht sicherstellen kann, dass jeder komplexe Muster wie Ihres konsequent verwendet. Ihr Nulllable Typ verhindert absolut nichts, um einen Entwickler davon abzuhalten, let x: number = null; x.toString() tun (oder etwas weniger Dummes, aber mit dem gleichen Effekt).

Und so weiter und so fort. Dieses Ticket ist _weit_ von geschlossen und das Problem ist immer noch zu 100% da.

Das kann doch nicht dein Ernst sein, oder?

Ich meine es ganz ernst.

Ich werde nicht jedes einzelne verpacken...

Warum nicht? Mit der ! Syntax oder was auch immer Sie sonst drängen, müssten Sie es sowieso tun.

oder noch besser NonNullable

Es sollte Nullable sein, was wir modellieren, sind Nullable-Typen, die einen Nicht-Nullable-Wert oder Null haben können und es wird explizit angegeben. Im Gegensatz zu herkömmlichen Typen, die IMMER einen Wert oder null haben können und number heißen, was bedeutet, dass Sie ihn vor der Verwendung auf null überprüfen sollten.

Das ist einfach zu viel Overhead.

Wo?

Ihr Code löst nicht das Problem der Verwendung einer Drittanbieterbibliothek.

Es tut.

Ich werde Sanitize nicht für jede Drittanbieterbibliothek oder sogar die integrierte DOM-API aufrufen, die ich aufrufe.

Sie müssen nicht. Alles, was Sie tun müssen, ist, die Definitionsdatei dieser Drittanbieter-Lib zu reparieren, indem Sie alles, was null sein kann, durch Nullable<*> ersetzen

Ebenso möchte ich Dokumentation für JS-Bibliotheken von Drittanbietern, dass eine Funktion null als Eingabe akzeptiert oder nicht.

Gleiche Sache. Definieren Sie diese Methode als Akzeptieren von Nullable<string> anstelle eines einfachen string .

Ihr Nulllable-Typ tut absolut nichts, um einen Entwickler daran zu hindern, let x: number = null; x.toString()

Es tut es in der Tat nicht. Sie müssen das Schlüsselwort null mit dem Linter sperren.

Komm schon, meine Lösung ist zu 95% funktionsfähig und zu 100% praktisch und ist HEUTE verfügbar, ohne schwierige Entscheidungen treffen zu müssen und alle auf den gleichen Stand zu bringen. Es ist die Frage, was Sie suchen: eine funktionierende Lösung, die nicht so aussieht, wie Sie es erwarten, aber trotzdem funktioniert, oder genau nach Ihren Wünschen mit allem Back Jack und Kirschen obendrauf

In 1.4 und 1.5 erlaubt der void-Typ keine Member von Object, einschließlich wie .toString(), also sollte type Nothing = void; ausreichen, anstatt das Modul zu benötigen (es sei denn, dies wurde in 1.6 wieder geändert). http://bit.ly/1OC5h8d

@aleksey-bykov das stimmt nicht wirklich. Sie müssen immer noch darauf achten, alle Werte zu bereinigen, die möglicherweise null sind. Sie erhalten keine Warnung, wenn Sie dies vergessen. Es ist auf jeden Fall eine Verbesserung (weniger Orte, an denen man vorsichtig sein sollte).

Um zu veranschaulichen, wie Sie mit Hacks nicht wirklich weit kommen, versuchen Sie es einfach

if (isNothing(something)) {
  console.log(something.toString())
}

Ich werde nicht jedes einzelne verpacken...

Warum nicht? Mit ! Syntax oder was auch immer Sie sonst drängen, Sie müssten es sowieso tun.

OK, das _könnte_ ich tun, wenn alles andere gelöst ist. Und wenn alles gelöst ist, kann TS vielleicht sogar etwas Sprachzucker dafür in Betracht ziehen.

oder noch besser NonNullable

Es sollte Nullable sein, was wir modellieren, sind Nullable-Typen, die einen Nicht-Nullable-Wert oder Null haben können und es wird explizit angegeben. Im Gegensatz zu herkömmlichen Typen, die IMMER einen Wert oder Null haben können und als Zahl bezeichnet werden, sollten Sie sie vor der Verwendung auf Null überprüfen.

Es ist nicht so, dass ich nur Null-Ausnahmen loswerden möchte. Ich möchte in der Lage sein, auch nicht-nullbare Typen mit statischer Sicherheit auszudrücken.
Angenommen, Sie haben eine Methode, die immer einen Wert zurückgibt. Wie toString() immer einen Nicht-Null-String zurück.
Welche Möglichkeiten hast du da?
let x: string = a.toString();
Dies ist nicht gut, da es keine statische Überprüfung gibt, dass x.length sicher ist. In diesem Fall ist es das, aber wie Sie sagten, könnte das eingebaute string durchaus null , also ist das der _Status quo_.
let x = sanitize(a.toString());
OK, jetzt kann ich es nicht ohne eine Nullprüfung verwenden, also ist der Code _sicher_. Aber ich möchte nicht überall if (isNothing(x)) hinzufügen, wo ich x in meinem Code verwende! Es ist sowohl hässlich als auch ineffizient, da ich sehr gut wissen konnte, dass x zur Kompilierzeit nicht null ist. (<string>x).length zu tun ist effizienter, aber es ist immer noch höllisch hässlich, dies überall tun zu müssen, wo Sie x möchten.

Was ich machen möchte ist:

let x = a.toString(); // documented, non-null type string (string! if you want to)
x.length; // statically OK

Sie können dies nicht ohne die richtige Sprachunterstützung erreichen, da alle Typen in JS (und TS 1.6) immer nullable sind.

Ich möchte sagen, dass die Programmierung mit nicht-optionalen (nullbaren) Typen eine sehr gute Praxis ist, so gut es geht. Was ich hier beschreibe, ist also ein wesentliches Szenario, keine Ausnahme.

Das ist einfach zu viel Overhead.

Wo?

Siehe meine vorherige Antwort.

Ihr Code löst nicht das Problem der Verwendung einer Drittanbieterbibliothek.

Es tut.

Nur die Hälfte des Problems ist gelöst. Angenommen, die Bibliotheksdefinitionen wurden wie von Ihnen vorgeschlagen aktualisiert: Jede Nullable-Eingabe oder jeder zurückgegebene Wert wird durch Nullable<X> anstelle von X ersetzt.

Ich kann null an eine Funktion übergeben, die keine null Parameter akzeptiert. OK, diese Tatsache ist jetzt _dokumentiert_ (was wir bereits mit JSDoc-Kommentaren oder was auch immer tun können), aber ich möchte, dass sie zur Kompilierzeit _erzwungen_ wird.
Beispiel: declare find<T>(list: T[], predicate: (T) => bool) . Beide Parameter sollten nicht null sein (ich habe Nullable nicht verwendet), aber ich kann find(null, null) tun. Ein weiterer möglicher Fehler besteht darin, dass die Deklaration besagt, dass ich vom Prädikat bool ungleich Null zurückgeben soll, aber ich kann Folgendes tun: find([], () => null) .

Ihr Nulllable-Typ tut absolut nichts, um einen Entwickler daran zu hindern, let x: number = null; x.toString()

Es tut es in der Tat nicht. Sie müssen das Schlüsselwort null mit dem Linter sperren.

Dies zu tun und stattdessen eine globale Variable nothing bereitzustellen, wird Ihnen in den Fällen, die ich im vorstehenden Punkt aufgelistet habe, nicht weiterhelfen, oder?

Aus meiner Sicht sind das nicht 95%. Ich habe das Gefühl, dass die Syntax für viele gängige Dinge viel schlechter geworden ist und ich immer noch nicht die statische Sicherheit habe, die ich mir wünsche, wenn ich über Typen ohne NULL-Werte spreche.

Sie müssen immer noch darauf achten, alle Werte zu bereinigen, die möglicherweise null sind.

Genau die gleiche Art von Trennungsjob, die Sie sowieso mit hypothetischen Nicht-Nullable-Typen machen müssten, von denen Sie hier sprechen. Sie müssten alle Ihre Definitionsdateien überprüfen und gegebenenfalls ! eingeben. Wie ist das anders?

Sie erhalten keine Warnung, wenn Sie dies vergessen.

Gleiche Sache. Gleiche Sache.

Veranschaulichen Sie, wie Sie mit Hacks nicht wirklich weit kommen, versuchen Sie es einfach

Obwohl Sie mit der Art und Weise, wie ich nichts definiert habe, Recht haben, das toString von Object , aber.. wenn wir die Idee von @Arnavion (der Verwendung von void für Nothing ) es macht plötzlich Klick

Betrachten

image

Aus meiner Sicht sind das nicht 95%. Ich habe das Gefühl, dass die Syntax für viele gängige Dinge viel schlechter geworden ist und ich immer noch nicht die statische Sicherheit habe, die ich mir wünsche, wenn ich über Typen ohne NULL-Werte spreche.

Mann, du hast mich gerade überzeugt, dieses Muster ist nichts für dich und wird es auch nie sein, bitte verwende es niemals in deinem Code, frag weiter nach den echten Nicht-Nullable-Typen, viel Glück, Entschuldigung, dass ich dich mit so einem Unsinn belästige

@aleksey-bykov
Ich verstehe, was Sie zu tun versuchen, obwohl ich den Eindruck habe, dass Sie noch nicht alle Fälle ausgebügelt haben und sich anscheinend Lösungen ausdenken, wenn jemand auf eine Lücke hinweist (wie die Ersetzung von null durch void im letzten Beispiel).

Am Ende _vielleicht_ wirst du es so hinbekommen, wie ich es will. Sie verwenden eine komplexe Syntax für Nullable-Typen, Sie haben jede Quelle von null im Programm mit einem Linter gesperrt, was schließlich dazu führen kann, dass Nullable-Typen nicht Null sind. Unterwegs müssen Sie alle davon überzeugen, ihre Bibliotheken mit Ihrer nicht standardmäßigen Konvention zu aktualisieren, sonst nützt es einfach nichts (wohlgemerkt, dies gilt für _alle_ Lösungen des Nullproblems, nicht nur für Ihre).

Ganz am Ende kann es Ihnen gelingen, das Typensystem zu verdrehen, um den eingebauten null Typ zu vermeiden und die gewünschten Schecks an Ihren Nothing Typ anhängen zu lassen.

Meinst du an dieser Stelle nicht, dass die Sprache es einfach unterstützen sollte? Die Sprache könnte alles ziemlich genau so implementieren, wie Sie es (intern) wollen. Aber obendrein haben Sie eine schöne Syntax, ausgebügelte Randfälle, keine Notwendigkeit für externe Linters und sicherlich eine bessere Übernahme durch Definitionen von Drittanbietern. Ist das nicht sinnvoller?

Meinst du an dieser Stelle nicht, dass die Sprache es einfach unterstützen sollte?

Reden wir darüber, was ich denke. Ich denke, es wäre schön, morgen in einer Welt aufzuwachen, in der TypeScript Typen ohne Nullwerte hat. Tatsächlich ist dies der Gedanke, mit dem ich jede Nacht ins Bett gehe. Aber es passiert nie am nächsten Tag und ich bin frustriert. Dann gehe ich an die Arbeit und stoße immer wieder auf das gleiche Problem mit Nullen, bis ich vor kurzem beschloss, nach einem Muster zu suchen, das mein Leben einfacher machen kann. Es sieht so aus, als hätte ich es gefunden. Wünschte ich immer noch, dass wir in TypeScript Nicht-Null-Werte hätten? Klar mache ich das. Kann ich ohne sie und Frustration leben? Sieht so aus, als ob ich es kann.

Wünschte ich immer noch, dass wir in TypeScript Nicht-Null-Werte hätten? Klar mache ich das.

Warum willst du dann dieses Thema schließen? :smiley:

Damit sollte die Anfrage geschlossen werden, da kein Problem mehr besteht. Fall abgeschlossen, Klasse abgewiesen.

Ich freue mich, dass Sie eine Lösung für Ihre Bedürfnisse gefunden haben, aber wie Sie hoffe ich immer noch, dass TS eines Tages richtig unterstützt wird. Und ich glaube, dass dies noch passieren kann (nicht bald, wohlgemerkt). Das C#-Team führt derzeit die gleichen Brainstormings durch, und vielleicht kann dies dazu beitragen, die Dinge voranzubringen. Wenn C# 7 es schafft, Nicht-Null-Typen zu erhalten (was noch nicht sicher ist), gibt es keinen Grund, warum TS dies nicht tun könnte.

@aleksey-bykov

Also um es zu vereinfachen

var nothing: void = null;
function isNothing<a>(value: a | void): value is void {
    return value == null;
}
var something = Math.random() > 0.5 ? 'hey!' : nothing;

und jetzt können Sie es auch ohne Funktionswrapper verwenden - richtige Typdefinitionen reichen aus.

Das ist im Grunde die Problemumgehung, die mich ursprünglich glücklich gemacht hat, bis ich entmutigt wurde, mich darauf zu verlassen .

Sie müssten alle Ihre Definitionsdateien überprüfen und ! gegebenenfalls. Wie ist das anders?

Der Hauptunterschied bei der Verwendung von standardmäßigen offiziellen Sprachfunktionen anstelle von Hacks besteht darin, dass die Community (DefinitelyTyped) da ist, um beim Schreiben, Testen und Teilen der Definitionsdateien zu helfen, anstatt all die zusätzliche Arbeit, die sich auf Ihre Projekte stapelt (naja, unten wirklich .) :Lächeln:).

Ich plädiere für " --noImplicitNull ". Das würde dazu führen, dass alle Typen standardmäßig nicht-nullbar sind und sofort Feedback zu potenziellen Problemen in bestehendem Code geben. Wenn ein "!" vorhanden ist, sollte es sich wie Swift verhalten, damit der Typprüfer die Möglichkeit von Nullen ignoriert (unsichere Umwandlung in Nicht-Null-Werte) - obwohl dies in Konflikt mit dem ES7+-Operator "eventual send" auf Versprechen steht, so dass es möglicherweise nicht das Beste ist Idee.

Wenn das in Bezug auf Abwärtskompatibilitätsbruch zu radikal erscheint, ist das wirklich meine Schuld. Ich sollte wirklich versuchen, die Zeit zu finden und es in einer Gabel zu versuchen, um zu beweisen, dass es nicht wirklich so radikal ist, wie es aussieht. In der Tat das Hinzufügen von "!" ist radikaler: Es wären mehr Änderungen erforderlich, um davon zu profitieren, da die meisten Werte nicht wirklich null sind; --noImplicitNull ist schrittweiser, da Sie mehr Fehler entdecken werden, indem Sie Typdefinitionen korrigieren, die fälschlicherweise Nicht-Null-Werte beanspruchen .

Wenn ich der Diskussion meine Stimme hinzufüge, gefällt mir, wie Kotlin das Problem gelöst hat, dh

  • eigene Variablen haben standardmäßig keine Nullable-Typen, ein '?' ändert das
  • externe Variablen sind nullable, ein '!!' überschreibt
  • eine Nullprüfung ist eine Typumwandlung
  • externer Code kann von einem Tool analysiert werden, das Annotationen generiert

Vergleichen:
1
2

  • wenn a|void gesperrt ist, kann man zu a|Nothing wechseln

Der Hauptunterschied bei der Verwendung von offiziellen Standardsprachfunktionen anstelle von Hacks besteht darin, dass die Community (DefinitelyTyped) dazu da ist, beim Schreiben, Testen und Teilen der Definitionsdateien zu helfen

  • Ich kann nicht sehen, warum die Verwendung von 100% legitimen Funktionen ein Hack ist
  • Überschätzen Sie nicht die Fähigkeiten der Community, viele (wenn nicht die meisten) Definitionen dort sind von geringer Qualität und sogar solche wie beispielsweise jquery können oft nicht ohne ein paar Änderungen so verwendet werden, wie sie geschrieben wurden
  • Auch hier wird ein Muster nie so gut sein wie ein vollwertiges Feature, ich hoffe, jeder versteht es, kein Zweifel
  • Ein Muster kann Ihre Probleme heute lösen, während ein Feature in der Nähe sein kann oder nicht
  • Wenn eine Situation effizient (bis zu 95%) durch die Verwendung von AB und C gelöst werden kann, warum sollte jemand D brauchen, um dasselbe zu tun? Lust auf Zucker? Warum sollten wir uns nicht auf etwas konzentrieren, das nicht durch ein Muster gelöst werden kann?

sowieso gibt es Problemlöser und Perfektionisten, da zeigte sich das Problem lösbar

(Wenn Sie sich nicht auf den void-Typ verlassen möchten, kann Nothing auch class Nothing { private toString: any; /* other Object.prototype members */ } was den gleichen Effekt hat. Siehe #1108)

_Hinweis: Hier geht es nicht darum, die Syntax von Bikesheding zu trennen. Bitte nimm es nicht
als solche._

Ich sage es einfach: Hier ist das Problem mit der vorgeschlagenen TS 1.6-Lösung
hier: kann durchaus funktionieren, gilt aber nicht für die Standardbibliothek oder
die meisten Definitionsdateien. Es gilt auch nicht für mathematische Operatoren. Falls Sie es wollen
um sich über redundante Typ-/Laufzeit-Boilerplates zu beschweren, versuchen Sie es mit generischen
Sammlungen funktionieren in C++ - das verblasst im Vergleich, besonders wenn Sie wollen
faule Iteration oder Sammlungen, die keine Arrays sind (oder noch schlimmer, versuchen Sie es)
beides kombinieren).

Das Problem mit der derzeit von @aleksey-bykov vorgeschlagenen Lösung ist, dass
es wird im Sprachkern nicht erzwungen. Zum Beispiel kannst du nicht tun
Object.defineProperty(null, "name", desc) - Sie erhalten einen TypeError von
das Zielobjekt null . Eine andere Sache: Sie können a keine Eigenschaft zuweisen
null Objekt, wie in var o = null; o.foo = 'foo'; . Du bekommst ein
ReferenceError IIRC. Diese vorgeschlagene Lösung kann diese Fälle nicht berücksichtigen.
Deshalb brauchen wir dafür Sprachunterstützung.


_Nun, ein bisschen milder Fahrradschuppen..._

Und was die Syntax angeht, mag ich die Prägnanz der "?string" und
"!string"-Syntax, aber das Endergebnis ist mir egal, solange ich es bin
nicht ThisIsANullableType<T, U, SomeRidiculousAndUnnecessaryExtraGeneric<V, W>> schreiben.

Am Mittwoch, den 29. Juli 2015, 16:10 Uhr schrieb Aleksey Bykov [email protected] :

  • wenn a|void gesperrt ist, kann man zu a|Nichts wechseln

    Der Hauptunterschied bei der Verwendung von standardmäßigen, offiziellen Sprachfunktionen
    statt Hacks ist, dass die Community (DefinitelyTyped) da ist, um zu helfen
    Schreiben, testen und teilen Sie die Definitionsdateien

    Ich kann nicht sehen, warum die Verwendung von 100% legitimen Funktionen ein Hack ist

    überschätze nicht die Fähigkeiten der Community, viele (wenn nicht die meisten)
    Definitionen sind von geringer Qualität und sogar solche wie, sagen wir, jquery ziemlich

    kann oft nicht wie geschrieben ohne ein paar Änderungen verwendet werden

    Auch hier wird ein Muster nie so gut sein wie ein vollwertiges Feature, hoffe

    Jeder versteht es, kein Zweifel

    Ein Muster kann Ihre Probleme heute lösen, während ein Merkmal möglicherweise oder möglicherweise

    nicht in der Nähe sein

    wenn eine Situation effizient (bis zu 95%) durch den Einsatz von AB . gelöst werden kann
    und C warum sollte jemand D brauchen, um dasselbe zu tun? Lust auf Zucker? warum sollte
    Wir konzentrieren uns auf etwas, das nicht durch ein Muster gelöst werden kann?

Problemlöser und Perfektionisten gibt es sowieso, wie sich gezeigt hat
Problem ist lösbar


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

Und könnten wir uns zumindest eine Vorstellung davon machen, wie die Struktur des Fahrradschuppens aussehen würde, bevor wir uns Gedanken darüber machen, welche Farbe wir anstreichen sollen? Das meiste, was ich nach den ersten ~10 Kommentaren bisher gesehen habe, war "Hey, wir wollen einen Fahrradschuppen bauen! In welcher Farbe sollen wir ihn streichen?" Nichts, um sicherzustellen, dass das Design strukturell solide ist. (Siehe #3192 und die erste Hälfte von #1206 für ein paar andere Beispiele dafür – der größte Teil des Lärms verstummte, als ich endlich einen ernsthaften Vorschlag mit einer logisch erstellten Syntax und einer vollständig spezifizierten Semantik machte.)

Denken Sie daran: Dies führt sehr wahrscheinlich zu einer umfassenden Umgestaltung und teilweisen Neuschreibung der Standarddefinitionen der Bibliothekstypen. Es wird auch dazu führen, dass ein Großteil der Typdefinitionen auf DefinitelyTyped für die erste Version von TS, die dies unterstützt, neu geschrieben werden müssen. Denken Sie also daran, dass dies definitiv kaputt gehen wird. (Eine Lösung könnte darin bestehen, immer standardmäßig auszugeben, selbst wenn nullbezogene Fehler auftreten, aber ein Flag a la --noImplicitAny bereitzustellen, um dieses Verhalten zu ändern.)

Zugegeben, ich bin hier etwas überfordert. Dennoch habe ich in Scholar.google nach "nullable Javascript" und "nullability Javascript" gesucht:
TypeScript verstehen
hat eine nette Typentabelle in TypeScript (Modul-Generika).

Abhängige Typen für JavaScript
scheint wichtig zu sein. Quellcode verschwunden

Vertrauen, aber überprüfen: Zwei-Phasen-Typisierung für DynamicSprachen
Verfeinerungstypen für Skriptsprachen
bietet eine Lösung basierend auf Typoskript.
("Die Abkürzung t? steht für t + null." ; klingt wie #186)

@afrische Vieles davon wird bereits praktisch in JS- Infernu , einen WIP-JS- 1 stützt, um seine Typen abzuleiten . Aber ich schweife ab...

[1] Haskell, OCaml usw. verwenden ebenfalls eine modifizierte Version für ihre Typsysteme.

Ich spiele derzeit mit einem TS-Fork, der davon ausgeht, dass Typen standardmäßig nicht nullablesbar sind und der (vorhandene) Null-Typ mit null referenzierbar macht, zB string | null . Ich habe auch eine Syntax für string? hinzugefügt, die lediglich eine Abkürzung für denselben Unionstyp ist.

Wenn Sie genau darüber nachdenken, ist es dasselbe wie bei @aleksey-bykov, aber wo null der Typ Nothing , integriert.

Und das ist gar nicht so kompliziert, denn das Typsystem beherrscht das alles schon ganz gut!

Wo es haarig wird, ist, dass wir einen reibungslosen Übergangspfad haben wollen. Wir brauchen eine gewisse Abwärtskompatibilität, _zumindest_ mit bestehenden Definitionsdateien (die Kompatibilität mit dem Projekt selbst könnte ein Opt-in-Flag sein, obwohl es besser wäre, wenn die Bedeutung eines Codestücks – sagen wir im Internet – nicht hängt nicht von globalen Compiler-Flags ab).

@afries Idee, string? in Ihrem eigenen Projekt zu verwenden, aber string! in .d.ts könnte ein neuer Ansatz sein. Obwohl mir die willkürliche Dualität, die sie erzeugt, nicht ganz gefällt. Warum ist string nullable und einige Dateien und andere nicht nullable? Scheint seltsam.

Wenn Sie es vorziehen, in einigen Dateien keine Nullable-Zeichenfolge zu verwenden und in anderen nicht.
Ich mag die Idee von @impinball , ein Opt-Out-Flag im Compiler zu haben und es als Breaking Change einzuführen (keuch: große Änderung gegenüber meinen früheren Argumenten). Vorausgesetzt natürlich, dass die neue Syntax keine Fehler verursacht, wenn das Opt-Out-Flag verwendet wird.

Dieser Thread ist über ein Jahr alt und es ist schwer herauszufinden, was alle relevanten Designs und Bedenken mit fast 300 Kommentaren und nur wenigen vom TS-Team selbst sind.

Ich plane, eine große Codebasis zu Flow, Closure Compiler oder TypeScript zu migrieren, aber der Mangel an Nullsicherheit in TypeScript ist ein echter Dealbreaker.

Ohne den ganzen Thread gelesen zu haben, kann ich nicht ganz herausfinden, was daran falsch ist, sowohl ! als auch ? NULL-Zulässigkeitsspezifizierer hinzuzufügen, während das vorhandene Verhalten für Typen, denen sie fehlen, beibehalten wird, also hier als Vorschlag :

Vorschlag

declare var foo:string // nullability unspecified
declare var foo:?string // nullable
declare var foo:!string // non-nullable

Mit ?string eine Obermenge von !string und string sowohl eine Obermenge als auch eine Teilmenge von beiden, dh die Beziehung zwischen string und beiden ?string und !string ist die gleiche wie die Beziehung zwischen any und allen anderen Typen, dh ein bloßer Typ ohne ! oder ? ist wie ein any in Bezug auf die Nichtigkeit.

Es gelten folgende Regeln:

| Typ | Enthält | Bietet | Kann null zuweisen? | Kann nicht null annehmen? |
| --- | --- | --- | --- | --- |
| T | T , !T , ?T | T , ?T , !T | ja | ja |
| ?T | T , !T , ?T | T , ?T | ja | nein (Typenschutz erforderlich) |
| !T | T , !T | T , ?T , !T | nein | ja |

Dies bietet Nullsicherheit, ohne vorhandenen Code zu beschädigen, und ermöglicht es vorhandenen Codebasen, die Nullsicherheit inkrementell einzuführen, genau wie any es vorhandenen Codebasen ermöglicht, Typsicherheit inkrementell einzuführen.

Beispiel

Hier ist ein Code mit einem Nullreferenzfehler:

function test(foo:Foo) { foo.method(); }
test(null);

Dieser Code geht immer noch. null immer noch Foo und Foo kann weiterhin als nicht null angenommen werden.

function test(foo:!Foo) { foo.method(); }
test(null);

Jetzt ist test(null) fehlerhaft, da null !Foo nicht zuordenbar ist.

function test(foo:?Foo) { foo.method(); }
test(null);

Jetzt ist foo.bar() fehlerhaft, da ein Methodenaufruf auf ?Foo nicht erlaubt ist (dh der Code muss nach null ).

Danke schön! Und diese Idee gefällt mir sehr gut. Obwohl aus Kompatibilitätsgründen,
könnten wir ?T und T nur Aliase machen, wie Array<T> und T[] ? Es
würde die Sache vereinfachen, und die undekorierte Version ist technisch immer noch
nullbar.

Am Fr, 28.08.2015, 07:14 Uhr schrieb Jesse Schalken [email protected] :

Dieser Thread ist über ein Jahr alt und es ist schwer herauszufinden, was alle
relevante Designs und Anliegen sind mit fast 300 Kommentaren und nur wenigen
vom TS-Team selbst.

Ich plane die Migration einer großen Codebasis zu Flow, Closure Compiler oder
TypeScript, aber der Mangel an Typsicherheit in TypeScript ist ein echter Dealbreaker.

Ohne den ganzen Thread gelesen zu haben, kann ich nicht ganz herausfinden, woran es liegt
indem man beides hinzufügt! und ? NULL-Zulässigkeitsspezifizierer unter Beibehaltung der
vorhandenes Verhalten für Typen, denen sie fehlen, also hier als Vorschlag:
Vorschlag

deklarieren var foo:string // nullability unspecifieddeclare var foo:?string // nullable,declare var foo:!string // non-nullable

Mit ?string eine Obermenge von !string und string sowohl eine Obermenge als auch eine Untermenge
von beiden, dh die Beziehung zwischen string und sowohl ?string als auch !string
ist die gleiche wie die Beziehung zwischen allen anderen Typen, dh a
nackter Typ ohne ! oder ? ist wie ein Any in Bezug auf die Nichtigkeit.

Es gelten folgende Regeln:
Typ Enthält Bietet Kann null zuweisen? Kann nicht null annehmen? TT, !T, ?TT,
?T, !T ja ja ?TT, !T, ?TT, ?T ja nein (Typschutz erforderlich) !TT, !TT,
?T, !T nein ja

Dies bietet Nullsicherheit, ohne vorhandenen Code zu beschädigen, und ermöglicht
vorhandene Codebasen, um die Nullsicherheit inkrementell einzuführen, genau wie alle anderen
ermöglicht es bestehenden Codebasen, Typsicherheit schrittweise einzuführen.
Beispiel

Hier ist ein Code mit einem Nullreferenzfehler:

Funktionstest (foo:Foo) {foo.method(); }test(null);

Dieser Code geht immer noch. null ist immer noch Foo zuweisbar und Foo kann immer noch
als nicht null angenommen werden.

Funktionstest (foo:!Foo) { foo.method(); }test(null);

Jetzt ist test(null) fehlerhaft, da null !Foo nicht zuordenbar ist.

Funktionstest (foo:?Foo) {foo.method(); }test(null);

Jetzt ist foo.bar() fehlerhaft, da ein Methodenaufruf auf ?Foo ist nicht erlaubt
(dh der Code muss auf null prüfen).


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

@impinball

Danke schön! Und diese Idee gefällt mir sehr gut. Könnten wir jedoch aus Kompatibilitätsgründen ?T und T nur Aliase machen, wie Array<T> und T[] ? Es würde die Dinge vereinfachen, und die undekorierte Version ist technisch immer noch nullbar.

Die _ganze Idee_ ist, dass T , ?T und !T drei verschiedene Dinge sind, und das _ist_ aus Kompatibilitätsgründen (Kompatibilität mit bestehendem TS-Code). Ich weiß nicht, wie ich es besser erklären soll, sorry. Ich meine, ich habe einen kleinen Tisch gemacht und alles.

Okay. Guter Punkt. Ich habe die Tabelle diesbezüglich falsch interpretiert und übersehen
im Fall des Wechsels bestehender Definitionsdateien, was zu Problemen mit
andere Anwendungen und Bibliotheken.

Am Fr, 28.08.2015, 11:43 Uhr schrieb Jesse Schalken [email protected] :

@impinball https://github.com/impinball

Danke schön! Und diese Idee gefällt mir sehr gut. Obwohl aus Kompatibilitätsgründen,
könnten wir ?T und T nur Aliase machen, wie Array?und T[]? Es würde
vereinfachen die Dinge, und die undekorierte Version ist immer noch technisch nullbar.

Die _ganze Idee_ ist, dass T, ?T und !T drei verschiedene Dinge sind,
und das _ist_ aus Kompatibilitätsgründen (Kompatibilität mit bestehenden TS
Code). Ich weiß nicht, wie ich es besser erklären soll, sorry. Ich meine, ich habe einen gemacht
kleiner Tisch und alles.


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

Entschuldigung, dass ich so ein Klugscheißer bin, aber das macht keinen Sinn

?string eine Obermenge von !string und string sowohl eine Obermenge als auch eine Teilmenge von beiden

aus der Mengenlehre wissen wir, dass eine Menge A eine Teilmenge und eine Obermenge einer Menge B genau dann ist, wenn A = B

wenn ja, wenn Sie "von beidem" sagen, kann dies nur alles-von-?string = alles-von-string und alles-von-!string = alles-von-string bedeuten

also letztendlich alles-von-?string = alles-von-!string

von wo alle aussteigen, der Bus fährt nirgendwo hin, der Bus ist außer Betrieb

@aleksey-bykov Wahrscheinlich ein Fall von schlechter Formulierung. Ich denke, er meint, dass string eher wie ?string | !string , wobei die freizügigsten Einschränkungen berücksichtigt werden.

Diese ganze Sache ähnelt im Umfang den immer häufiger vorkommenden @Nullable und @NonNull / @NotNull Typannotationen zur Kompilierzeit und wie sie mit Typen ohne Anmerkungen arbeiten.

Und ich wäre definitiv für ein neues Flag für Typen, die implizit als Nicht-Nullwerte gelten, insbesondere Primitive.

Ein Flag "non-nullable by default" wäre schön, würde aber auch eine große Anzahl von DefinitelyTyped-Definitionen zerstören. Dies schließt die Definitionen von Angular und Node ein, und es würde viel mühsame Arbeit erfordern, sie zu korrigieren. Es kann auch einen Backport erfordern, damit die neuen Typen immer noch nicht als Syntaxfehler geparst werden, aber die NULL-Zulässigkeit wird nicht überprüft. Ein solcher Backport wäre die einzige praktische Möglichkeit, mit einem solchen Flag den Bruch zu mindern, insbesondere da Definitionen für Nullable-Typen aktualisiert werden. (Die Leute verwenden immer noch TypeScript 1.4 in der Entwicklung, insbesondere in größeren Projekten.)

@impinball

Die Leute verwenden immer noch TypeScript 1.4 in der Entwicklung, insbesondere in größeren Projekten.

Ich denke, die Kompatibilitätsgeschichte für dieses Feature ist bereits schwer genug, ohne zu versuchen, neue Definitionen mit alten Compilern kompatibel zu machen (FWIW-Typen, die nicht Null sind, sind fast trivial zum aktuellen TS-Compiler hinzuzufügen).

Wenn die Leute auf alten TS bleiben, sollten sie alte Definitionen verwenden. (Ich weiß, das ist nicht praktikabel).
Ich meine, die Dinger werden sowieso kaputt gehen. Bald wird TS 1.6 Kreuzungstypen hinzufügen und Definitionen, die es verwenden, werden auch nicht kompatibel sein.

Übrigens, ich bin überrascht, dass die Leute bei 1.4 bleiben, es gibt so viel zu lieben in 1.5.

@aleksey-bykov

Entschuldigung, dass ich so ein Klugscheißer bin, aber das macht keinen Sinn

Ich gehe davon aus, dass Sie sich selbst als "intelligenten Arsch" bezeichnen, weil Sie aus dem Rest des Beitrags genau wissen, was "Übermenge und Teilmenge von beiden" bedeuten soll. Natürlich ist es unsinnig, wenn es auf tatsächliche Sets angewendet wird.

any kann einem beliebigen Typ zugewiesen werden (er verhält sich wie eine Obermenge aller Typen) und wie ein beliebiger Typ behandelt (er verhält sich wie eine Untermenge aller Typen). any bedeutet "Typprüfung für diesen Wert deaktivieren".

string kann sowohl von !string als auch von ?string zugewiesen werden (es verhält sich wie eine Obermenge von ihnen) und kann sowohl als !string als auch als ?string (es verhält sich wie eine Teilmenge von ihnen). string bedeutet "aus der Nullbarkeitsprüfung für diesen Wert aussteigen", dh das aktuelle TS-Verhalten.

@impinball

Und ich wäre definitiv für ein neues Flag für Typen, die implizit als Nicht-Nullwerte gelten, insbesondere Primitive.

@RyanCavanaugh sagte ausdrücklich:

Flags, die die Semantik einer Sprache ändern, sind eine gefährliche Sache. [...] Es ist wichtig, dass jemand, der sich ein Stück Code ansieht, dem Typsystem "folgen" kann und die daraus gezogenen Schlussfolgerungen versteht. Wenn wir anfangen, eine Reihe von Flaggen zu haben, die die Regeln der Sprache ändern, wird dies unmöglich.

Die einzig sichere Methode besteht darin, die Semantik der Zuweisbarkeit gleich zu halten und zu ändern, was ein Fehler ist und was nicht von einem Flag abhängt, ähnlich wie es heute mit noImplicitAny funktioniert.

Aus diesem Grund behält mein Vorschlag das vorhandene Verhalten für Typen bei, denen ein NULL-Zulässigkeitsspezifizierer fehlt ( ! oder ? ). Das hinzuzufügende _only_ safe-Flag wäre eines, das Typen ohne NULL-Zulässigkeitsspezifizierer nicht zulässt, dh es braucht nur Code zu übergeben und verursacht einen Fehler, es ändert seine Bedeutung nicht. Ich nehme an, dass noImplicitAny aus dem gleichen Grund erlaubt war.

@jesseschalken

Ich meinte das im Zusammenhang mit implizit typisierten Variablen, die nicht initialisiert sind
zu null oder undefined in Fällen wie diesen:

var a = new Type(); // type: !Type
var b = 2; // type: !number
var c = 'string'; // type: !string
// etc...

Entschuldigung für die Verwirrung.

Am Fr, 28.08.2015, 19:54 Uhr schrieb Jesse Schalken [email protected] :

@impinball https://github.com/impinball

Und ich wäre definitiv für eine neue Flagge für Typenwesen
implizit als nicht-nullable angesehen, insbesondere Primitive.

@RyanCavanaugh https://github.com/RyanCavanaugh sagte ausdrücklich:

Flags, die die Semantik einer Sprache ändern, sind eine gefährliche Sache. […]
Es ist wichtig, dass jemand, der sich ein Stück Code ansieht, "mitverfolgen" kann.
mit dem Typensystem und verstehen die Schlussfolgerungen, die gemacht werden. Wenn
Wir beginnen mit einer Reihe von Flaggen, die die Regeln der Sprache ändern,
das wird unmöglich.

Die einzig sichere Art ist, die Semantik von . beizubehalten
Zuordenbarkeit gleich und ändern, was ein Fehler ist oder was nicht abhängig ist
auf einer Flagge, ähnlich wie noImplicitAny heute funktioniert.

Deshalb behält mein Vorschlag das bestehende Verhalten für fehlende Typen bei
ein NULL-Zulässigkeitsspezifizierer (! oder ?). Das hinzuzufügende _nur_ sichere Flag wäre
eine, die Typen ohne NULL-Zulässigkeitsspezifizierer verbietet, dh nur
nimmt übergebenen Code und verursacht einen Fehler, es ändert seine Bedeutung nicht. ich
Angenommen, noImplicitAny wurde aus dem gleichen Grund zugelassen.


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

@jeffmcaffer was Sie sagen string ist eine Vereinigung von Werten von !string und ?string bedeutet alles von !string oder/und ?string gehört zu string

@impinball Auch hier würde ein solches Flag die Bedeutung von bestehendem Code ändern, was (aus dem Lesen der Kommentare von @RyanCavanaugh ) nicht erlaubt ist. export var b = 5; exportiert jetzt ein !number wo zuvor ein number exportiert wurde.

Ja, gewissermaßen. Um etwas technischer zu sein, akzeptiert es die Gewerkschaft, aber
es stellt den Schnittpunkt bereit. Grundsätzlich kann jeder Typ als
string und ein string können für beide Typen übergeben werden.

Am Freitag, 28. August 2015, 20:20 Uhr schrieb Aleksey Bykov [email protected] :

@impinball https://github.com/impinball was du damit sagen willst
Das Ändern der ursprünglichen Bedeutung der Wörter Obermenge und Untermenge kann immer noch sein
leicht in Mengen artikuliert: die Wertemenge von string ist a
_Vereinigung_ der Werte von !string und ?string bedeutet alles von !string
oder/und ?String gehört zum String


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

Offensichtlich ist es nicht standardmäßig aktiviert. Und die meisten Definitionsdateien
wäre davon unberührt. Technisch gesehen ändert sich alles, was nicht rein ist
additiv (auch das Hinzufügen eines neuen Arguments zu einer Funktion ist nicht in JavaScript enthalten) hat
die Fähigkeit, Anwendungen zu unterbrechen. Es hat einen ähnlichen Umfang wie
noImplicitAny , da es eine etwas explizitere Eingabe erzwingt. Und ich
glaube nicht, dass es noch viel mehr kaputt machen könnte, zumal das einzige
Dies kann sich auf andere Dateien auswirken, wenn Sie aus dem tatsächlichen TypeScript exportieren
Quelldaten. (Das andere Flag hat zahlreiche Definitionsdateien beschädigt und deaktiviert
eine häufige Art von Ententests.)

Am Fr, 28.08.2015, 20:21 Uhr schrieb Jesse Schalken [email protected] :

@impinball https://github.com/impinball Auch hier würde sich eine solche Flagge ändern
die Bedeutung des vorhandenen Codes, der (aus dem Lesen von @RyanCavanaugh
https://github.com/RyanCavanaughs Kommentare) ist nicht erlaubt. Exportvariable
b = 5; exportiert jetzt eine !Nummer, wo zuvor eine Nummer exportiert wurde.


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

Offensichtlich ist es nicht standardmäßig aktiviert.

Es ändert immer noch die Bedeutung des Codes. Sobald Sie das tun, können Sie weder TypeScript-Code lesen und sagen "Ich weiß, was das bedeutet", noch können Sie eine Bibliothek schreiben, die jemand anderes verwendet, da die Bedeutung des Codes davon abhängt, wofür Flags verwendet werden kompilieren, und das ist nicht akzeptabel. Sie haben die Sprache buchstäblich in zwei Teile geteilt.

Aus diesem Grund behält mein Vorschlag das vorhandene Verhalten für Typen bei, denen ein NULL-Zulässigkeitsspezifizierer fehlt ( ? oder ! ).

@jesseschalken
Wenn Sie die Bedeutung des vorhandenen Codes mit 100%iger Genauigkeit bewahren möchten (und erreichen), sollten Sie die Idee der Nullsicherheit bei Typoskript aufgeben. In der Praxis wird keine Lösung anwendbar sein.

? und ! auf jede Typanmerkung setzen zu müssen, ist bereits ausführlich genug, um es nicht zu tun, aber Sie müssten auch alle Typrückschlüsse aufgeben. let x = 3 leitet number heute ab. Wenn Sie hier eine Änderung nicht akzeptieren, müssen Sie alles explizit eingeben, um die Vorteile der Nullsicherheit zu nutzen. Ich bin auch nicht bereit, etwas zu tun.

Wenn die Vorteile die Nachteile überwiegen, können einige Zugeständnisse gemacht werden. Wie von @impinball darauf hingewiesen wurde, noImplicitAny genau das getan. Es ist ein Flag, das neue Fehler in Ihrem Code erzeugt, wenn Sie es aktivieren. Daher kann das Kopieren und Einfügen von Code aus dem Internet oder auch nur die Verwendung von TS-Bibliotheken kaputt gehen, wenn der Code, den Sie aufnehmen, nicht unter der Annahme von noImplicitAny (und mir ist es passiert).

Ich denke, die Nullsicherheit kann auf ähnliche Weise eingeführt werden. Die Bedeutung von Code ist dieselbe und er läuft mit exakt identischer Semantik. Aber unter einem neuen Flag (sagen wir noImplicitNull oder was auch immer) erhalten Sie zusätzliche Prüfungen und Warnungen/Fehler von Code, der nicht mit der Annahme von noImplicitNull .

Ich denke, die Nullsicherheit kann auf ähnliche Weise eingeführt werden. Die Bedeutung von Code ist dieselbe
und es läuft mit exakt identischer Semantik. Aber unter einem neuen Flag (sag noImplicitNull oder was auch immer)
Sie erhalten zusätzliche Prüfungen und Warnungen/Fehler von Code, der nicht mit geschrieben wurde
die noImplicitNull-Annahme.

Ich mag diesen Ansatz und es scheint ein logischer Weg zu sein, die Sprache weiterzuentwickeln. Ich hoffe, dass es im Laufe der Zeit zum De-facto-Standard wird, so wie Typisierungen normalerweise mit noImplicitAny im Hinterkopf geschrieben werden.

Ich denke jedoch, dass es bei der schrittweisen Einführung wichtig ist, sicherzustellen, dass vorhandener Code modulweise migriert werden kann und dass neuer Code, der mit expliziten Nullen geschrieben wurde, problemlos mit vorhandenem Code funktionieren kann, der implizite Nullen verwendet.

Wie wäre es also damit:

  • Mit -noImplicitNull wird T zu einem Alias ​​für !T . Andernfalls ist die NULL-Zulässigkeit von T unbekannt.
  • Das Flag sollte auf Modulbasis überschreibbar sein, indem oben eine Anmerkung hinzugefügt wird, z. <strong i="18">@ts</strong>:implicit_null . Dies ähnelt der Art und Weise, wie Flow die Typprüfung auf Modulbasis ermöglicht.
  • Das Konvertieren einer vorhandenen Codebasis erfolgt, indem zuerst die Compileroption -noImplicitNull hinzugefügt wird und dann alle vorhandenen Module mit dem Flag ' annotiert werden . Dieser zweite Schritt könnte trivial automatisiert werden.
  • Beim Importieren eines Moduls muss eine Richtlinie zum Konvertieren der Typen vorhanden sein, wenn die importierten und importierenden Module unterschiedliche Implizitheitseinstellungen haben.

Für diesen letzten Punkt gibt es verschiedene Optionen, wenn ein Modul mit expliziten Nullen eines mit impliziten Nullen importiert.

  • Ein extremer Ansatz wäre, die NULL-Zulässigkeit von T Typen als unbekannt zu behandeln und vom Importer zu verlangen, den Typ explizit in ?T oder !T umzuwandeln. Das würde viele Anmerkungen im Angerufenen erfordern, aber sicher sein.
  • Ein anderer Ansatz wäre, alle importierten T Typen als ?T . Dies würde auch viele Anmerkungen im Anrufer erfordern.
  • Schließlich könnten alle importierten T Typen als !T . Dies wäre in einigen Fällen natürlich falsch, aber es könnte die pragmatischste Option sein. Ähnlich wie eine Variable vom Typ any einem Wert vom Typ T zugewiesen werden kann.

Die Gedanken?

@jods4 Das noImplicitAny Flag _ändert_ nicht_ die Bedeutung von existierendem Code, es erfordert nur, dass der Code explizit über etwas ist, was sonst implizit wäre.

| Code | Flagge | Bedeutung |
| --- | --- | --- |
| interface Foo { blah; } | | interface Foo { blah:any; } |
| interface Foo { blah; } | noImplicitAny | Fehler, expliziter Typ erforderlich |
| var foo = 'blah' | | var foo:string = 'blah' |
| var foo = 'blah' | noImplicitNull | var foo:!string = 'blah' |

Mit noImplicitNull hatten Sie vorher eine Variable, in die null geschrieben werden konnte. Jetzt haben Sie eine Variable, in die null _nicht_ geschrieben werden kann. Das ist ein ganz anderes Tier als noImplicitAny .

@RyanCavanaugh hat bereits Flags ausgeschlossen, die die Semantik des vorhandenen Codes ändern. Wenn Sie die ausdrücklichen Anforderungen des TS-Teams glatt ignorieren, wird dieses Ticket noch ein Jahr herumhängen.

@jesseschalken Entschuldigung, aber ich sehe den Unterschied nicht.
Vor noImplicitAny Sie dies in Ihrem Code haben:
let double = x => x*2;
Es kompiliert und funktioniert gut. Aber sobald Sie noImplicitAny einschalten, gibt der Compiler einen Fehler aus und sagt, dass x implizit beliebig ist. Sie müssen Ihren Code ändern, damit er mit dem neuen Flag funktioniert:
let double = (x: any) => x*2 oder besser noch let double = (x: number) => x*2 .
Beachten Sie, dass der Compiler zwar einen Fehler ausgelöst hat, aber dennoch einwandfrei funktionierenden JS-Code ausgibt (es sei denn, Sie deaktivieren die Ausgabe bei Fehlern).

Die Situation mit Nullen ist meiner Meinung nach ziemlich ähnlich. Nehmen wir zur Diskussion an, dass T mit dem neuen Flag nicht-nullfähig ist und T? oder T | null die Vereinigung des Typs T und null .
Vorher hattest du vielleicht:
let foo: string; foo = null; oder auch nur let foo = "X"; foo = null die genauso auf string .
Es kompiliert und funktioniert gut. Aktivieren Sie nun das neue noImplicitNull Flag. Plötzlich gibt TS einen Fehler aus, der anzeigt, dass Sie null etwas zuweisen können, das nicht explizit als solches deklariert wurde. Aber bis auf den Tippfehler gibt Ihr Code immer noch _den gleichen_, korrekten JS-Code aus.
Mit dem Flag müssen Sie Ihre Absicht explizit angeben und den Code ändern:
string? foo; foo = null;

Was ist also wirklich der Unterschied? Code wird immer einwandfrei ausgegeben und sein Laufzeitverhalten hat sich überhaupt nicht geändert. In beiden Fällen erhalten Sie Fehler vom Typsystem und Sie müssen Ihren Code ändern, damit er in Ihren Typdeklarationen expliziter ist, um sie zu beseitigen.

Außerdem ist es in beiden Fällen möglich, Code, der unter dem strikten Flag geschrieben wurde, zu nehmen und mit deaktiviertem Flag zu kompilieren, und es funktioniert immer noch gleich und ohne Fehler.

@robertknight Ganz nah an meinem aktuellen Denken.

Für Module / Definitionen, die sich nicht für die strikten Nicht-Null-Typen entschieden haben, sollte T im Grunde bedeuten: alle Arten von Null-Fehlern für diesen Typ ausschalten. Der Versuch, es auf T? zu erzwingen, kann immer noch zu Kompatibilitätsproblemen führen.

Das Problem ist, dass heute einige T tatsächlich nicht-nullierbar sind und andere null-ablesbar sind. Erwägen:

// In a strict module, function len does not accept nulls
function len(x: string): number { return x.length; }
// In a legacy module, some calls to len
let abc: string = "abc";
len(abc);

Wenn Sie im Legacy-Modul einen Alias ​​​​von string bis string? , wird der Aufruf zu einem Fehler, da Sie eine möglicherweise Null-Variable an einen Parameter übergeben, der keine Nullen zulässt.

@jods4 Lies meinen Kommentar noch einmal. Schau auf den Tisch. Ich weiß nicht, wie ich es deutlicher ausdrücken soll.

Ihr Beispiel wurde explizit erstellt, um zu Ihrer Schlussfolgerung zu gelangen, indem Sie die Definition von foo und die Zuweisung von foo nebeneinander setzen. Bei noImplicitAny sich die einzigen Fehler, die speziell aus dem Code resultieren, der geändert werden muss (da er seine Bedeutung nicht geändert hat, musste er nur expliziter ausgedrückt werden). Bei noImplicitNull war der Code, der den Fehler verursachte, die _Zuweisung_ an foo aber der Code, der geändert werden musste, um ihn zu beheben (um die alte Bedeutung zu haben), war die _definition_ von foo . Dies ist von entscheidender Bedeutung, da das Flag _die Bedeutung der Definition von foo _ geändert hat. Die Zuweisung und die Definition können sich offensichtlich auf verschiedenen Seiten einer Bibliotheksgrenze befinden, für die das Flag noImplicitNull die _Bedeutung_ der öffentlichen Schnittstelle dieser Bibliothek geändert hat!

die Flagge hat die Bedeutung der Definition von foo geändert.

Ja, das ist wahr. Es änderte sich von „Ich habe nicht die geringste Ahnung, ob die Variable null halten kann oder nicht – und es ist mir einfach egal“ in „Diese Variable ist nullfrei“. Es besteht eine 50/50-Chance, dass es richtig war, und wenn nicht, müssen Sie Ihre Absicht in der Erklärung präzisieren. Am Ende ist das Ergebnis dasselbe wie bei noImplicitAny : Sie müssen Ihre Absicht in der Deklaration deutlicher machen.

Die Zuweisung und die Definition können offensichtlich auf verschiedenen Seiten einer Bibliotheksgrenze liegen

Tatsächlich normalerweise die Deklaration in der Bibliothek und die Verwendung in meinem Code. Jetzt:

  1. Wenn sich die Bibliothek für strikte Nullen entschieden hat, muss sie ihre Typen korrekt deklarieren. Wenn die Bibliothek sagt, dass sie strikte Nulltypen hat und x nicht nullfähig ist, dann versuche ich, eine Null zuzuweisen _ist tatsächlich ein Fehler, der gemeldet werden sollte_.
  2. Wenn sich die Bibliothek nicht für strikte Nullen entschieden hat, sollte der Compiler keine Fehler für seine Verwendung ausgeben.

Dieses Flag (genau wie noImplicitAny ) kann nicht aktiviert werden, ohne Ihren Code anzupassen.

Ich verstehe Ihren Standpunkt, aber ich würde sagen, dass wir den Bedeutungscode nicht _ändern_; vielmehr _drücken wir Bedeutungen aus, die heute nicht vom Typsystem erfasst werden.

Das ist nicht nur gut, weil es Fehler im heutigen Code abfängt, sondern ich würde auch sagen, dass es ohne einen solchen Schritt in TS niemals verwendbare Typen geben wird, die nicht Null sind.

Gute Nachrichten für nicht nullbare Typen! Es sieht so aus, als ob das TS-Team damit einverstanden ist, bahnbrechende Änderungen in TS-Updates einzuführen!
Wenn Sie dies sehen:
http://blogs.msdn.com/b/typescript/archive/2015/09/02/announcing-typescript-1-6-beta-react-jsx-better-error-checking-and-more.aspx
Sie führen eine Breaking Change (sich gegenseitig ausschließende optionale Syntax) mit einem neuen Dateityp ein, und sie führen eine Breaking Change _ohne_ den neuen Dateityp ein (betrifft jeden).
Das ist ein Präzedenzfall, mit dem wir nicht nullbare Typen argumentieren können (zB eine .sts- oder strikte Typescript-Erweiterung und entsprechende .sdts).

Jetzt müssen wir nur noch herausfinden, ob der Compiler versucht, nach undefinierten Typen oder nur Null-Typen zu suchen (und welche Syntax) und wir haben einen soliden Vorschlag.

@jbondc Sehr interessante Lektüre. Ich freue mich zu sehen, dass meine Intuition, dass die Migration zu NNBD (standardmäßig nicht nullbar) einfacher ist als die Migration zu optional nicht nullbar, durch Studien bestätigt wurde (eine Größenordnung weniger Änderungen bei der Migration und im Fall von Dart 1- 2 Anmerkungen pro 1000 Codezeilen erforderlich Nullitätsänderungen, nicht mehr als 10 selbst in nulllastigem Code)

Ich bin mir nicht sicher, ob die Komplexität des Dokuments wirklich die Komplexität von Nicht-Nullable-Typen widerspiegelt. Im Abschnitt Generika werden beispielsweise drei Arten von formalen Typparametern erörtert und dann gezeigt, dass Sie diese nicht wirklich benötigen. In TS wäre null einfach der Typ des vollständig leeren Datensatzes (keine Eigenschaften, keine Methoden), während {} der Root-Nicht-Null-Typ wäre, und dann sind nicht-nullbare Generics einfach G<T extends {}> - Es ist nicht nötig, mehrere Arten von formalen Typparametern zu diskutieren.

Außerdem scheint es, dass sie viel nicht-essentiellen Zucker anbieten, wie var !x

Die Umfrage zu existierenden Sprachen, die sich mit dem gleichen Problem beschäftigt haben, ist jedoch das wahre Juwel.

Beim Lesen des Dokuments wurde mir klar, dass Optional / Maybe Typen leistungsfähiger sind als Nullable-Typen, insbesondere in einem generischen Kontext - hauptsächlich wegen der Möglichkeit, Just(Nothing) zu codieren. Wenn wir beispielsweise eine generische Map-Schnittstelle haben, die Werte des Typs T und get die je nach Vorhandensein eines Schlüssels einen Wert zurückgeben können oder nicht:

interface Map<T> {
  get(s:string):Maybe<T>
}

nichts hindert T daran, vom Typ Maybe<U> ; der Code funktioniert einwandfrei und gibt Just(Nothing) wenn ein Schlüssel vorhanden ist, aber einen Nothing Wert enthält, und wird einfach Nothing wenn der Schlüssel überhaupt nicht vorhanden ist.

Im Gegensatz dazu, wenn wir nullable Typen verwenden

interface Map<T> {
  get(s:string):T?
}

dann ist es unmöglich, zwischen einem fehlenden Schlüssel und einem Nullwert zu unterscheiden, wenn T nullfähig ist.

In jedem Fall ist die Fähigkeit, NULL-Werte von Nicht-NULL-Werten zu unterscheiden und die verfügbaren Methoden und Eigenschaften entsprechend zu modellieren, eine Voraussetzung für jede Art von Typsicherheit.

@jbondc Dies ist ein sehr

Ich finde es beruhigend, dass Studien zeigen, dass 80% der Deklarationen tatsächlich nicht null sind oder dass es nur 20 Nichtigkeitsanmerkungen pro KLOC gibt (S. 21). Wie im Dokument erwähnt, ist dies ein starkes Argument für standardmäßig nicht null, was auch mein Gefühl war.

Ein weiteres Argument für Nicht-Null ist, dass es ein saubereres Typsystem schafft: null ist ein eigener Typ, T ist nicht-null und T? ist ein Synonym für T | null . Da TS bereits einen Gewerkschaftstyp hat, ist alles schön, sauber und funktioniert gut.

Angesichts der Liste der neueren Sprachen, die dieses Problem angegangen sind, denke ich wirklich, dass eine neue moderne Programmiersprache dieses seit langem bestehende Problem behandeln und Null-Fehler in Codebasen reduzieren sollte. Dies ist ein viel zu häufiges Problem für etwas, das im Typsystem modelliert werden sollte. Ich hoffe immer noch, dass TS es eines Tages bekommt.

Ich fand die Idee des Operators T! faszinierend und möglicherweise nützlich. Ich dachte an ein System, bei dem T ein Nicht-Null-Typ ist, T? T | null . Aber es hat mich gestört, dass Sie nicht wirklich eine generische API erstellen konnten, die selbst bei einer NULL-Eingabe ein Ergebnis ungleich Null garantiert. Ich habe keine guten Anwendungsfälle, aber theoretisch könnte ich das nicht originalgetreu modellieren: function defined(x) { return x || false; } .
Mit dem Umkehroperator ungleich Null könnte man Folgendes tun: function defined<T>(x: T): T! | boolean . Das heißt, wenn defined ein T defined zurückgibt, ist es garantiert ungleich null, selbst wenn die generische T Einschränkung nullable war, sagen wir string? . Und ich glaube nicht, dass es schwer ist, in TS zu modellieren: Wenn T! , wenn T ein Union-Typ ist, der den Typ null , geben Sie den Typ zurück, der sich aus dem Entfernen von null ergibt.

@spion

Beim Lesen des Dokuments wurde mir klar, dass optionale/vielleicht-Typen leistungsfähiger sind als Nullable-Typen

Sie können Maybe Strukturen verschachteln, Sie können tatsächlich nicht null verschachteln.

Dies ist eine interessante Diskussion im Zusammenhang mit der Definition neuer APIs, aber beim Mapping vorhandener APIs gibt es wenig Auswahl. Wenn Sie die Sprachzuordnung auf Maybe auf Null setzen, wird dieser Vorteil nicht genutzt, es sei denn, die Funktion wird vollständig neu geschrieben.

Maybe kodiert zwei unterschiedliche Informationen: ob es einen Wert gibt und was der Wert ist. Nehmen Sie Ihr Map Beispiel und betrachten Sie C#, dies ist offensichtlich, ausgehend von Dictionary<T,K> :
bool TryGet(K key, out T value) .
Beachten Sie, dass, wenn C# Tupel hat (vielleicht C# 7), dies im Grunde dasselbe ist wie:
(bool hasKey, T value) TryGet(K key)
Das ist im Grunde ein Maybe und ermöglicht das Speichern von null .

Beachten Sie, dass JS seine eigene Art hat, mit diesem Problem umzugehen und eine Menge neuer interessanter Probleme aufwirft: undefined . Ein typisches JS Map würde undefined wenn der Schlüssel nicht gefunden wird, andernfalls seinen Wert, einschließlich null .

Verwandter Vorschlag für C# 7 – https://github.com/dotnet/roslyn/issues/5032

Ist euch klar, dass das Problem nicht gelöst ist, es sei denn, ihr modelliert undefined auf die gleiche Weise?
Andernfalls werden alle Ihre Nullprobleme einfach durch undefinierte Probleme ersetzt (die imo sowieso häufiger vorkommen).

@Griffork

alle Ihre Nullprobleme werden einfach durch undefinierte Probleme ersetzt

Nein, warum sollten sie?
Meine Nullprobleme werden verschwinden und meine undefinierten Probleme bleiben.

Es stimmt, undefined ist immer noch ein Problem. Aber das hängt stark von Ihrem Programmierstil ab. Ich programmiere fast ohne undefined außer wenn der Browser sie mir aufzwingt, was bedeutet, dass 90% meines Codes mit Nullprüfungen sicherer wären.

Ich programmiere fast ohne undefined, außer wenn der Browser sie mir aufzwingt

Ich hätte gedacht, dass JavaScript auf Schritt und Tritt undefined aufzwingt.

  • Nicht initialisierte Variablen. let x; alert(x);
  • Ausgelassene Funktionsargumente. let foo = (a?) => alert(a); foo();
  • Zugriff auf nicht vorhandene Array-Elemente. let x = []; alert(x[0]);
  • Zugriff auf nicht vorhandene Objekteigenschaften. let x = {}; alert(x['foo']);

Null hingegen tritt in weniger und vorhersehbareren Situationen auf:

  • DOM-Zugriff. alert(document.getElementById('nonExistent'));
  • Antworten von Webdiensten von Drittanbietern (da JSON.stringify undefined ) . { name: "Joe", address: null }
  • Regex.

Aus diesem Grund verbieten wir die Verwendung von null , konvertieren alle null , die wir über die Überweisung erhalten, in undefined und verwenden immer eine strikte Gleichheitsprüfung für undefined . Das hat sich bei uns in der Praxis gut bewährt.

Folglich stimme ich zu, dass das undefined Problem das häufigere ist.

@NoelAbrahams Codierungsstil, ich sage dir :)

Nicht initialisierte Variablen

Ich initialisiere immer Variablen und habe noImplicitAny aktiviert, also wäre let x sowieso ein Fehler. Der nähere let x: any = null , den ich in meinem Projekt verwenden würde, ist

Optionale Funktionsparameter

Ich verwende Standardparameterwerte für optionale Parameter, das scheint mir sinnvoller zu sein (Ihr Code _wird_ den Parameter irgendwie lesen und verwenden, nicht wahr?). Also für mich: function f(name?: string = null, options?: any = {}) .
Der Zugriff auf den rohen undefined Parameterwert wäre für mich ein _außergewöhnlicher_ Fall.

Zugriff auf nicht vorhandene Array-Elemente

Dies ist etwas, was ich bemühe, in meinem Code nicht zu tun. Ich überprüfe die Länge meiner Arrays so, dass sie nicht über die Grenzen hinausgeht und verwende keine spärlichen Arrays (oder versuche, leere Slots mit Standardwerten wie null , 0 , ... zu füllen).
Auch hier können Sie sich einen Sonderfall einfallen lassen, in dem ich das tun würde, aber das wäre eine _Ausnahme_, nicht die Regel.

Zugriff auf nicht vorhandene Objekteigenschaften.

So ziemlich das Gleiche wie bei Arrays. Meine Objekte sind typisiert, wenn ein Wert nicht verfügbar ist, setze ich ihn auf null , nicht auf undefined . Auch hier können Sie Randfälle finden (wie eine Wörterbuchuntersuchung), aber sie sind _Randfälle_ und nicht repräsentativ für meinen Codierungsstil.

In allen _aussergewöhnlichen_ Fällen, in denen ich undefined zurückbekomme, gehe ich sofort auf das Ergebnis von undefined und propagiere es nicht weiter oder "arbeite" damit. Typisches reales Beispiel in einem fiktiven TS-Compiler mit Nullprüfungen:

let cats: Cat[];
// Note that find returns undefined if there's no cat named Kitty
let myFavoriteCat = cats.find(c => c.name === 'Kitty'); 
if (myFavoriteCat === undefined) {
  // Immediately do something to compensate here:
  // return false; or 
  // myFavoriteCat = new Cat('Kitty'); or
  // whatever makes sense.
}
// Continue with assurance that myFavoriteCat is not null (it was an array of non-nullable cats after all).

Aus diesem Grund verbieten wir die Verwendung von null , konvertieren alle über die Leitung empfangenen null in undefined und verwenden immer eine strikte Gleichheitsprüfung für undefined . Das hat sich bei uns in der Praxis gut bewährt.

Daraus verstehe ich, dass Sie einen ganz anderen Kodierungsstil verwenden als ich. Wenn Sie undefined Grunde überall verwenden, dann profitieren Sie viel weniger von statisch überprüften Nulltypen als ich
möchten.

Ja, das Ding ist wegen undefined nicht 100% wasserdicht und ich glaube nicht, dass man eine einigermaßen brauchbare Sprache erstellen kann, die in dieser Hinsicht 100%ig korrekt ist. JS führt undefined an zu vielen Stellen ein.

Aber wie ich hoffe, dass Sie aus meinen obigen Antworten erkennen können, gibt es bei entsprechendem Codierungsstil _viel_ von Nullprüfungen zu profitieren. Zumindest ist meine Meinung, dass es in meiner Codebasis helfen würde, viele dumme Fehler zu finden und zu verhindern und die Produktivität in meiner Teamumgebung zu steigern.

@jods4 , es war interessant, deinen Ansatz zu lesen.

Ich denke, der Einwand, den ich gegen diesen Ansatz habe, ist, dass es anscheinend viele Regeln gibt, die eingehalten werden müssen - es ist eher wie der Kommunismus vs. der freie Markt :smile:

Das TS-Team hat intern eine ähnliche Regel wie wir für seinen eigenen Stil.

@NoelAbrahams „Verwende undefined “ ist genauso eine Regel wie „Verwende null “.

Auf jeden Fall ist Konsistenz der Schlüssel und ich möchte kein Projekt, bei dem ich nicht sicher bin, ob die Dinge null oder undefined (oder eine leere Zeichenfolge oder Null) sein sollen. Zumal TS bei diesem Problem derzeit nicht weiterhilft...

Ich weiß, dass TS eine Regel hat, um undefined gegenüber null zu bevorzugen.

Warum ich lieber null als undefined :

  1. Es funktioniert auf eine bekannte Weise für unsere Entwickler, viele stammen aus statischen OO-Sprachen wie C#.
  2. Nicht initialisierte Variablen werden in vielen Sprachen normalerweise als Code-Geruch angesehen, nicht sicher, warum dies in JS anders sein sollte. Machen Sie Ihre Absicht deutlich.
  3. Obwohl JS eine dynamische Sprache ist, ist die Leistung mit statischen Typen besser, die sich nicht ändern. Es ist effizienter, eine Eigenschaft auf null als sie auf delete .
  4. Es unterstützt den sauberen Unterschied zwischen null das definiert ist, aber das Fehlen von Werten anzeigt, und undefined , das ... undefiniert ist. Ein Ort, an dem der Unterschied zwischen beiden offensichtlich ist: optionale Parameter. Das Nichtübergeben eines Parameters führt zu undefined . Wie _übergeben_ Sie den leeren Wert, wenn Sie dafür undefined in Ihrer Codebasis verwenden? Mit null gibt es hier kein Problem.
  5. Es legt einen sauberen Pfad zur Nullprüfung fest, wie in diesem Thread besprochen, was mit undefined nicht wirklich praktikabel ist. Obwohl ich vielleicht von diesem Tag träume.
  6. Sie müssen eine Entscheidung für die Konsistenz treffen, IMHO ist null so gut wie undefined .

Ich denke, der Grund, warum ich undefined gegenüber null vorziehe, ist wegen
Standardargumente und Konsistenz mit obj.nonexistentProp Rückgabe
undefined .

Abgesehen davon verstehe ich den Fahrradschuppen nicht über das, was als null zählt
genug, um zu verlangen, dass die Variable nullable ist.

Am Dienstag, 8. September 2015, 06:48 Uhr schrieb jods [email protected] :

@NoelAbrahams https://github.com/NoelAbrahams "Use undefined" ist wie
viel eine Regel wie "Null verwenden".

Auf jeden Fall ist Konsistenz der Schlüssel und ich möchte kein Projekt, wo ich bin
nicht sicher, ob die Dinge null oder undefiniert sein sollen (oder ein leeres)
Zeichenfolge oder Null). Zumal TS damit aktuell nicht weiterhilft
Ausgabe...

Ich weiß, dass TS eine Regel hat, um undefined gegenüber null zu bevorzugen, ich bin gespannt, ob dies
ist eine willkürliche "aus Gründen der Konsistenz" oder wenn es mehr gibt
Argumente für die Wahl.

Warum _ich_ lieber null als undefined verwende:

  1. Es funktioniert auf eine bekannte Weise für unsere Entwickler, viele kommen aus statischem OO
    Sprachen wie C#.
  2. Nicht initialisierte Variablen werden in der Regel als Code-Geruch angesehen
    viele Sprachen, ich bin mir nicht sicher, warum es in JS anders sein sollte. Mach dein
    Absicht klar.
  3. Obwohl JS eine dynamische Sprache ist, ist die Leistung besser mit
    statische Typen, die sich nicht ändern. Es ist effizienter, eine Eigenschaft auf zu setzen
    null als löschen.
  4. Es unterstützt die saubere Differenz zwischen null, die definiert ist, aber
    bedeutet die Abwesenheit von Wert und undefiniert, was ... undefiniert ist.
    Ein Ort, an dem der Unterschied zwischen den beiden offensichtlich ist: optional
    Parameter. Die Nichtübergabe eines Parameters führt zu undefined. Wie geht es dir?
    _pass_ den leeren Wert, wenn du undefined dafür in deinem Code verwendest
    Base? Mit null gibt es hier kein Problem.
  5. Es legt einen sauberen Weg zur Nullprüfung vor, wie hier besprochen
    Thread, der mit undefined nicht wirklich praktikabel ist. Obwohl vielleicht
    Ich träume davon.
  6. Sie müssen sich für Konsistenz entscheiden, IMHO ist null so gut wie
    nicht definiert.


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

@impinball
Können wir aufhören, "bikeshedding" in jeder Github-Diskussion zu verwenden? Wir können mit Sicherheit sagen, dass die Verwendung von undefined oder null eine Teampräferenz ist; aber die Frage, ob versucht werden soll, undefined in die Nullprüfungen einzubeziehen (oder nicht) und wie es funktionieren würde, ist nicht trivial. Also verstehe ich überhaupt nicht, wie das Bikeshedding ist?

Ich habe eine Gabel von TS 1.5 mit nicht nullbaren Typen gebaut und es war überraschend einfach. Aber ich denke, es gibt zwei schwierige Probleme, die einen Konsens erfordern, um im offiziellen TS-Compiler Typen ohne Nullwerte zu verwenden. Beide wurden oben ohne klare Schlussfolgerung ausführlich diskutiert:

  1. Was machen wir mit undefined ? (meine Meinung: es ist immer noch überall und ungeprüft)
  2. Wie handhaben wir die Kompatibilität mit bestehendem Code, insbesondere Definitionen? (Meine Meinung: Opt-in-Flag, zumindest pro Definitionsdatei. Das Aktivieren des Flags ist "breaking", da Sie möglicherweise Null-Anmerkungen hinzufügen müssen.)

Meine persönliche Meinung ist, dass null und undefiniert behandelt werden sollten
gleichwertig für Zwecke der Nichtigkeit. Beide werden für diesen Anwendungsfall verwendet,
das Fehlen eines Wertes darstellen. Einer ist, dass der Wert nie existiert hat,
und die andere ist, dass der Wert einmal existierte und nicht mehr existiert. Beide
sollte für die NULL-Zulässigkeit zählen. Die meisten JS-Funktionen geben undefiniert zurück, aber viele
DOM- und Bibliotheksfunktionen geben null zurück. Beide dienen demselben Anwendungsfall. Daher,
sie sollten gleich behandelt werden.

In der Bikeshedding-Referenz ging es um die Codestilargumente, über die
sollte verwendet werden, um das Fehlen eines Wertes darzustellen. Einige streiten für
einfach null, einige argumentieren für einfach undefiniert und einige argumentieren für a
Mischung. Dieser Vorschlag sollte sich nicht auf nur einen davon beschränken.

Am Dienstag, 8. September 2015, 13:28 Uhr schrieb jods [email protected] :

@impinball https://github.com/impinball
Können wir aufhören, "bikeshedding" in jeder Github-Diskussion zu verwenden? Wir können sicher
sagen, dass die Verwendung von undefined oder null eine Teampräferenz ist; aber das problem
ob versucht werden soll, undefined in die Nullprüfungen aufzunehmen (oder nicht) und wie
es würde funktionieren ist nicht trivial. Also ich verstehe nicht, wie das im Fahrradschuppen ist
erster Platz?

Ich habe eine Gabel von TS 1.5 mit nicht nullbaren Typen gebaut und es war
überraschend einfach. Aber ich denke, es gibt zwei schwierige Probleme, die
einen Konsens benötigen, um im offiziellen TS-Compiler Typen ohne NULL-Werte zu verwenden,
beide wurden oben ausführlich diskutiert, ohne eine klare Schlussfolgerung zu ziehen:

  1. Was machen wir mit undefiniert? (meine meinung: es ist immer noch überall
    und ungeprüft)
  2. Wie gehen wir insbesondere mit der Kompatibilität mit bestehendem Code um?
    Definitionen? (meine Meinung: Opt-in-Flag, zumindest pro Definitionsdatei.
    Das Einschalten des Flags ist "breaking", da Sie möglicherweise null hinzufügen müssen
    Anmerkungen.)


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

@jods4 Ich entnehme Ihrem Punkt 2, dass Ihr Fork standardmäßig auch den Nicht-Nullable-Typ

Kannst du es bitte verlinken? Ich möchte es gerne ausprobieren.

@impinball
Ich würde auch gerne (etwas) Sicherheit gegen undefined , aber es ist ziemlich allgegenwärtig.

Können wir insbesondere ein Array von Typen definieren, die keine NULL-Werte zulassen?
Können wir angesichts der Tatsache, dass ein außerhalb der Grenzen liegender (oder spärlicher) Array-Zugriff undefined zurückgibt, bequem Arrays definieren und verwenden?
Ich denke, dass die Anforderung, dass alle Arrays nullfähig sein müssen, in der Praxis zu viel Aufwand ist.

Sollten wir die Typen null und undefined unterscheiden? Das ist nicht schwer: T | null , T | undefined , T | null | undefined und kann eine einfache Antwort auf die obige Frage geben. Aber was ist mit der Abkürzungssyntax: Wofür steht T? ? Sowohl null als auch undefiniert? Brauchen wir zwei verschiedene Abkürzungen?

@Arnavion
In TS sind bereits Null- und Undefinierte Typen vorhanden.
Meine Meinung war:

  1. Machen Sie alle Typen nicht-nullierbar (einschließlich abgeleiteter Typen);
  2. Geben Sie dem Nulltyp einen Namen ( null ), den Sie in Typdeklarationen verwenden können.
  3. Entfernen Sie die Erweiterung von null auf any ;
  4. Führen Sie die Syntax-Kurzform T? , die mit T | null identisch ist;
  5. Entfernen Sie implizite Konvertierungen von null in einen anderen Typ.

Ohne Zugriff auf meine Quellen denke ich, das ist der Kern der Sache. Der vorhandene Null-Typ und das wunderbare TS-Typsystem (insbesondere Union-Typen und Typwächter) erledigen den Rest.

Ich habe meine Arbeit auf github noch nicht festgeschrieben, daher kann ich vorerst keinen Link teilen. Ich wollte zuerst den Kompatibilitätsschalter codieren, aber ich war sehr beschäftigt mit anderen Dingen:(
Der Kompatibilitätsschalter ist viel komplizierter <_<. Aber es ist wichtig, weil sich der TS-Compiler im Moment mit vielen Fehlern selbst kompiliert und viele vorhandene Tests fehlschlagen.
Aber es scheint tatsächlich gut mit brandneuem Code zu funktionieren.

Lassen Sie mich zusammenfassen, was ich bisher von einigen Kommentatoren gesehen habe, in der Hoffnung, dass ich veranschaulichen kann, wie sich dieses Gespräch im Kreis dreht:
Problem: Bestimmte 'Sonderfälle'-Werte finden ihren Platz in Code, der nicht dafür ausgelegt ist, damit umzugehen, weil sie anders behandelt werden als jeder andere Typ in der Sprache (dh null und undefiniert).
Ich: Warum legen Sie nicht einfach Programmierstandards fest, damit diese Probleme nicht auftreten?
Sonstiges: weil es schön wäre, wenn sich die Absicht in der Typisierung widerspiegeln könnte, weil es dann nicht von jedem Team, das in Typskript arbeitet, anders dokumentiert wird und weniger falsche Annahmen über Bibliotheken von Drittanbietern auftreten.
Everone: Wie gehen wir mit diesem Problem um?
Sonstiges: Machen wir Nullen einfach strenger und verwenden Sie Standards, um mit undefined umzugehen!

Ich sehe nicht, wie dies möglicherweise als Lösung angesehen werden kann, wenn undefined viel mehr ein Problem ist als null!

Haftungsausschluss: Dies spiegelt nicht die Einstellung aller hier wider, es ist nur so, dass ich dies hervorheben möchte.
Dies wird nur dann akzeptiert, wenn es sich um eine Lösung eines Problems handelt, nicht um eine kleine Lösung für den kleineren Teil eines Problems.

Verdammtes Telefon!
*alle

* es ist genug herausgekommen, dass ich es hervorheben wollte.

Auch der letzte Absatz sollte lauten "nur so kann dieser Vorschlag"

@jods4 Ich würde sagen, dass es von bestimmten Konstrukten abhängt. In einigen Fällen können Sie in diesen Fällen die Nicht-NULL-Zulässigkeit garantieren, z. B. in den folgenden Fällen:

declare const list: T![];

for (const entry of list) {
  // `entry` is clearly immutable here.
}

list.forEach(entry => {
  // `entry` is clearly immutable here.
})

list.map(entry => {
  // `entry` is clearly immutable here.
})

In diesem Fall müsste der Compiler eine Menge Logik haben, um sicherzustellen, dass das Array in Grenzen überprüft wird:

declare const list: T![]

for (let i = 0; i < list.length; i++) {
  // This could potentially fail if the compiler doesn't correctly do the static bounds check.
  const entry: T![] = list[i];
}

Und in diesem Fall können Sie nicht garantieren, dass der Compiler überprüfen kann, ob der Zugriff auf entry in Grenzen ist, ohne tatsächlich Teile des Codes auszuwerten:

declare const list: T![]

const end = round(max, list.length);

for (let i = 0; i < end; i++) {
  const entry: T![] = list[i];
}

Es gibt einige einfache, offensichtliche, aber es gibt einige schwierigere.

@impinball Tatsächlich sind moderne APIs wie map , forEach oder for..of in Ordnung, da sie Elemente überspringen, die nie initialisiert oder gelöscht wurden. (Sie enthalten Elemente, die auf undefined , aber unser hypothetischer nullsicherer TS würde dies verbieten.)

Aber der klassische Array-Zugriff ist ein wichtiges Szenario und ich würde mir dafür eine gute Lösung wünschen. Eine komplexe Analyse wie von Ihnen vorgeschlagen durchzuführen, ist eindeutig nicht möglich, außer in trivialen Fällen (jedoch wichtige Fälle, da sie häufig vorkommen). Beachten Sie jedoch, dass selbst wenn Sie beweisen könnten, dass i < array.length es nicht beweist, dass das Element initialisiert ist.

Betrachten Sie das folgende Beispiel: Was sollte TS Ihrer Meinung nach tun?

let array: T![] = [];  // an empty array of non-null, non-undefined T
// blah blah
array[4] = new T();  // Fine for array[4], but it means array[0..3] are undefined, is that OK?
// blah blah
let i = 2;
// Note that we could have an array bounds guard
if (i < array.length) {
  let t = array[i];  // Inferred type would be T!, but this is actually undefined :(
}

Es gibt auch das Problem mit Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
  get() { return 10 },
})

Am Mittwoch, den 9. September 2015, 17:49 Uhr schrieb jods [email protected] :

@impinball https://github.com/impinball Tatsächlich moderne APIs wie Map,
forEach oder for..of sind in Ordnung, weil sie Elemente überspringen, die es nie gab
initialisiert oder gelöscht. (Sie enthalten Elemente, die auf gesetzt wurden
undefiniert, aber unser hypothetischer nullsicherer TS würde das verbieten.)

Aber der klassische Array-Zugriff ist ein wichtiges Szenario, und ich möchte
sehe da eine gute lösung. Komplexe Analysen wie von Ihnen vorgeschlagen durchzuführen, ist
eindeutig nicht möglich, außer in trivialen Fällen (aber wichtige Fälle, da
sie sind üblich). Aber beachte, selbst wenn du beweisen könntest, dass ich <
array.length beweist nicht, dass das Element initialisiert ist.

Betrachten Sie das folgende Beispiel: Was sollte TS Ihrer Meinung nach tun?

let Array: T![] = []; // ein leeres Array von nicht null, nicht undefinierten T// bla bla
Array[4] = neues T(); // Gut für Array[4], aber es bedeutet, dass array[0..3] undefiniert sind, ist das in Ordnung? // blah blahlet i = 2; // Beachten Sie, dass wir eine Array-Grenze haben könnten guardif (i < array. Länge) {
sei t = Array[i]; // Der abgeleitete Typ wäre T!, aber das ist eigentlich undefiniert :(
}


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

Alles, was Sie tun müssen, ist:

  1. Machen Sie die Typen Null und Undefiniert referenzierbar.
  2. Verhindern Sie, dass null und undefinierte Werte irgendetwas zugewiesen werden können.
  3. Verwenden Sie einen Union-Typ mit Null und/oder Undefined, wo die NULL-Zulässigkeit impliziert wurde.

Es ist in der Tat eine mutige bahnbrechende Veränderung und sieht besser aus für eine Sprache
Verlängerung.
Am 10. September 2015 um 09:13 schrieb "Isiah Meadows" [email protected] :

Es gibt auch das Problem mit Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
get() { return 10 },
})

Am Mittwoch, den 9. September 2015, 17:49 Uhr schrieb jods [email protected] :

@impinball https://github.com/impinball Tatsächlich moderne APIs wie
Karte,
forEach oder for..of sind in Ordnung, weil sie Elemente überspringen, die es nie gab
initialisiert oder gelöscht. (Sie enthalten Elemente, die auf gesetzt wurden
undefiniert, aber unser hypothetischer nullsicherer TS würde das verbieten.)

Aber der klassische Array-Zugriff ist ein wichtiges Szenario, und ich möchte
sehe da eine gute lösung. Komplexe Analysen wie von Ihnen vorgeschlagen durchzuführen, ist
eindeutig nicht möglich, außer in trivialen Fällen (aber wichtige Fälle, da
sie sind üblich). Aber beachte, selbst wenn du beweisen könntest, dass ich <
array.length beweist nicht, dass das Element initialisiert ist.

Betrachten Sie das folgende Beispiel: Was sollte TS Ihrer Meinung nach tun?

let Array: T![] = []; // ein leeres Array von nicht null, nicht undefinierten T//
bla bla
Array[4] = neues T(); // Gut für Array[4], aber das bedeutet Array[0..3] sind
undefiniert, ist das in Ordnung? // blah blahlet i = 2; // Beachte, dass wir ein . haben könnten
Arraygrenzen guardif (i < array.length) {
sei t = Array[i]; // Der abgeleitete Typ wäre T!, aber das ist es tatsächlich
nicht definiert :(
}


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


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

@impinball
Dein Beispiel finde ich gut. Wenn Sie defineProperty auf diese Weise verwenden, verlassen Sie die TS-Sicherheitsbox und betreten den dynamischen JS-Bereich, finden Sie nicht? Ich glaube nicht, dass ich jemals defineProperty direkt im TS-Code aufgerufen habe.

@aleksey-bykov

sieht eher für eine Spracherweiterung geeignet aus.

Ein weiteres Problem bei diesem Vorschlag besteht darin, dass er einen geringeren Wert hat, wenn er nicht allgemein akzeptiert wird.
Schließlich brauchen wir aktualisierte TS-Definitionen und ich glaube nicht, dass dies passieren wird, wenn es sich um eine selten verwendete, inkompatible Erweiterung oder Abzweigung von TS handelt.

Ich weiß, dass typisierte Arrays eine Garantie haben, weil IIRC sie a . werfen
ReferenceError beim Laden und Speichern von Arrays außerhalb der Grenzen. Regelmäßige Arrays und
arguments-Objekte geben undefiniert zurück, wenn der Index außerhalb der Grenzen liegt. In meinem
Meiner Meinung nach ist das ein JS-Sprachfehler, aber es würde definitiv behoben werden
das Web brechen.

Die einzige Möglichkeit, dies zu "korrigieren", besteht in ES6 über einen Konstruktor, der einen Proxy zurückgibt.
und sein Instanzprototyp und Selbstprototyp auf das ursprüngliche Array eingestellt
Konstrukteur. Etwas wie das:

Array = (function (A) {
  "use strict";
  function check(target, prop) {
    const i = +prop;
    if (prop != i) return target[prop];
    if (i >= target.length) {
      throw new ReferenceError();
    }
    return i;
  }

  function Array(...args) {
    return new Proxy(new Array(...args), {
      get(target, prop) {
        return target[check(target, prop)];
      },
      set(target, prop, value) {
        return target[check(target, prop)] = value;
      },
    });
  }

  Array.prototype = A.prototype;
  Array.prototype.constructor = Array
  Object.setPrototypeOf(Array, A);
  return Array;
})(Array);

(Anmerkung: ungetestet, auf einem Telefon getippt...)

Am Do, 10. September 2015, 10:09 Uhr schrieb jods [email protected] :

@impinball https://github.com/impinball
Dein Beispiel finde ich gut. Die Verwendung von defineProperty auf diese Weise ist
Verlassen Sie die TS-Sicherheitsbox und betreten Sie den dynamischen JS-Bereich, nicht
du denkst? Ich glaube nicht, dass ich defineProperty jemals direkt im TS-Code aufgerufen habe.

@aleksey-bykov https://github.com/aleksey-bykov

sieht eher für eine Spracherweiterung geeignet aus.

Ein weiteres Problem bei diesem Vorschlag ist, dass er, wenn er nicht allgemein akzeptiert wird,
einen geringeren Wert hat.
Irgendwann brauchen wir aktualisierte TS-Definitionen und ich glaube nicht, dass dies der Fall sein wird
passieren, wenn es sich um eine selten verwendete, inkompatible Erweiterung oder Abzweigung von TS handelt.


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

@impinball Nicht sicher, ob es überhaupt etwas zu "reparieren" gibt ...
Das ist die Semantik von JS und TS muss sie irgendwie unterbringen.

Was wäre, wenn wir nur ein (etwas anderes) Markup für die Deklaration eines Arrays mit geringer Dichte gegenüber einem Array ohne geringe Dichte haben und das nicht mit geringer Dichte automatisch initialisieren lassen (vielleicht können die Programmierer einen Wert oder eine Gleichung für die Initialisierung bereitstellen). Auf diese Weise können wir erzwingen, dass dünn besetzte Arrays den Typ T|undefined (was mit for... of und anderen 'sicheren' Operationen in den Typ T geändert würde) und die Typen von nicht dünn besetzten Arrays in Ruhe lassen.

//not-sparse
var array = [arrlength] => index*3;
var array = <number[]>[3];
//sparse
var array = [];

Offensichtlich ist dies nicht die endgültige Syntax.
Das zweite Beispiel würde jeden Wert mit der Compiler-Standardeinstellung für diesen Typ initialisieren.
Dies bedeutet, dass Sie sie für Arrays ohne Sparse eingeben müssen, da Sie sie sonst vermutlich in etwas umwandeln müssen, nachdem sie vollständig initialisiert wurden.
Außerdem sollten wir einen Nicht-Sparse-Typ für Arrays benötigen, damit Programmierer ihre Arrays in Nicht-Sparse umwandeln können.

@Griffork
Ich weiß nicht... es wird verwirrend.

Auf diese Weise können wir erzwingen, dass dünn besetzte Arrays den Typ T|undefined (was sich in den Typ T ändern würde, indem for... of und andere "sichere" Operationen verwendet werden).

Wegen einer Macke in JS funktioniert das nicht. Angenommen let arr: (T|undefined)[] .
Es steht mir also frei: arr[0] = undefined .
Wenn ich das tue, dann _will_ mit diesen "sicheren" Funktionen undefined für den ersten Slot zurückgeben. In arr.forEach(x => ...) kann man also nicht sagen, dass x: T . Es muss immer noch x: T|undefined .

Das zweite Beispiel würde jeden Wert mit der Compiler-Standardeinstellung für diesen Typ initialisieren.

Dies ist im Geiste nicht sehr TS-ähnlich. Vielleicht liege ich falsch, aber es scheint mir, dass die TS-Philosophie darin besteht, dass Typen nur eine zusätzliche Schicht über JS sind und keinen Einfluss auf Codegen haben. Dies hat Perf-Implikationen im Interesse der teilweisen Typkorrektheit, die ich nicht ganz mag.

TS kann Sie offensichtlich nicht vor allem in JS schützen und es gibt mehrere Funktionen / Konstrukte, die Sie von gültigem TS aufrufen können, die dynamische Auswirkungen auf den Laufzeittyp Ihrer Objekte haben und die statische TS-Typanalyse unterbrechen.

Wäre es schlecht, wenn dies ein Loch im Typensystem wäre? Ich meine, dieser Code ist wirklich nicht üblich: let x: number[] = []; x[3] = 0; und wenn das die Art von Dingen ist, die Sie tun möchten, dann sollten Sie vielleicht Ihr Array let x: number?[] deklarieren.

Es ist nicht perfekt, aber ich denke, es ist gut genug für die meisten Anwendungen in der realen Welt. Wenn Sie ein Purist sind, der ein Soundsystem möchte, dann sollten Sie sich unbedingt eine andere Sprache ansehen, denn TS-System ist sowieso nicht Sound. Was denkst du?

Aus diesem Grund habe ich gesagt, dass Sie auch die Fähigkeit benötigen, in ein nicht dünn besetztes Array umzuwandeln
type, sodass Sie ein Array ohne die Leistung selbst initialisieren können
Einschlag.
Ich würde mich mit einer Unterscheidung zwischen (was gemeint sein soll) begnügen
Arrays und Nicht-Sparse-Arrays nach Typ.

Für diejenigen, die nicht wissen, warum das wichtig ist, es ist der gleiche Grund wie du
würde einen Unterschied zwischen T und T|null wünschen.

Am Freitag, den 11. September 2015 um 9:11 Uhr schrieb jods [email protected] :

@Griffork https://github.com/Griffork
Ich weiß nicht... es wird verwirrend.

Auf diese Weise können wir erzwingen, dass dünn besetzte Arrays den Typ T|undefined haben (was
Wechsel zu Typ T mit for... of und anderen 'sicheren' Operationen)

Wegen einer Macke in JS funktioniert das nicht. Angenommen lass arr:
(T|undefiniert)[].
Es steht mir also frei: arr[0] = undefined.
Wenn ich das tue, dann _will_ mit diesen "sicheren" Funktionen undefiniert zurückgeben
für den ersten Steckplatz. In arr.forEach(x => ...) kann man also nicht sagen, dass x: T.
Es muss immer noch x: T|undefined sein.

Das zweite Beispiel würde jeden Wert mit der Compiler-Standardeinstellung initialisieren
für diesen Typ.

Dies ist im Geiste nicht sehr TS-ähnlich. Vielleicht liege ich falsch, aber es scheint mir
diese TS-Philosophie ist, dass Typen nur eine zusätzliche Ebene über JS sind
und sie haben keinen Einfluss auf Codegen. Dies hat Perf-Implikationen im Interesse von
partielle Typkorrektheit, die mir nicht ganz gefällt.

TS kann dich offensichtlich nicht vor allem in JS schützen und es gibt
mehrere Funktionen / Konstrukte, die Sie von gültigen TS aufrufen können, die über
dynamische Effekte auf den Laufzeittyp Ihrer Objekte und brechen statische TS
Typanalyse.

Wäre es schlecht, wenn dies ein Loch im Typensystem wäre? Ich meine, dieser Code
ist wirklich nicht üblich: let x: number[] = []; x[3] = 0; und wenn das so ist
Dinge, die Sie tun möchten, sollten Sie Ihr Array vielleicht als let . deklarieren
x: Zahl?[].

Es ist nicht perfekt, aber ich denke, es ist gut genug für die meisten Anwendungen in der realen Welt.
Wenn Sie ein Purist sind, der ein Soundsystem möchte, dann sollten Sie es auf jeden Fall
schau dir eine andere Sprache an, denn TS-Typ-System ist sowieso nicht stichhaltig. Was
denkst du?


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

@jods4 Was ich mit " Reparieren ", was IMHO ein JS-Sprachdesignfehler ist. Nicht TypeScript, sondern in _JavaScript selbst_.

@jods Ich beschwere mich über JS, nicht über TS. Ich gebe zu, es ist Off-Topic.

Am Do, 10. September 2015, 19:19 Uhr schrieb Griffork [email protected] :

Aus diesem Grund habe ich gesagt, dass Sie auch die Fähigkeit benötigen, in ein nicht dünn besetztes Array umzuwandeln
type, sodass Sie ein Array ohne die Leistung selbst initialisieren können
Einschlag.
Ich würde mich mit einer Unterscheidung zwischen (was gemeint sein soll) begnügen
Arrays und Nicht-Sparse-Arrays nach Typ.

Für diejenigen, die nicht wissen, warum das wichtig ist, es ist der gleiche Grund wie du
würde einen Unterschied zwischen T und T|null wünschen.

Am Freitag, den 11. September 2015 um 9:11 Uhr schrieb jods [email protected] :

@Griffork https://github.com/Griffork
Ich weiß nicht... es wird verwirrend.

Auf diese Weise können wir erzwingen, dass dünn besetzte Arrays den Typ T|undefined haben (was
Wechsel zu Typ T mit for... of und anderen 'sicheren' Operationen)

Wegen einer Macke in JS funktioniert das nicht. Angenommen lass arr:
(T|undefiniert)[].
Es steht mir also frei: arr[0] = undefined.
Wenn ich das tue, dann _will_ mit diesen "sicheren" Funktionen undefiniert zurückgeben
für den ersten Steckplatz. In arr.forEach(x => ...) kann man also nicht sagen, dass x: T.
Es muss immer noch x: T|undefined sein.

Das zweite Beispiel würde jeden Wert mit der Compiler-Standardeinstellung initialisieren
für diesen Typ.

Dies ist im Geiste nicht sehr TS-ähnlich. Vielleicht liege ich falsch, aber es scheint mir
die TS-Philosophie ist, dass Typen nur eine zusätzliche Schicht darüber sind
JS
und sie haben keinen Einfluss auf Codegen. Dies hat Perf-Implikationen im Interesse von
partielle Typkorrektheit, die mir nicht ganz gefällt.

TS kann dich offensichtlich nicht vor allem in JS schützen und es gibt
mehrere Funktionen / Konstrukte, die Sie von einem gültigen TS aufrufen können, die
verfügen über
dynamische Effekte auf den Laufzeittyp Ihrer Objekte und brechen statische TS
Typanalyse.

Wäre es schlecht, wenn dies ein Loch im Typensystem wäre? Ich meine, dieser Code
ist wirklich nicht üblich: let x: number[] = []; x[3] = 0; und wenn das so ist
Dinge, die Sie tun möchten, dann sollten Sie vielleicht Ihr Array deklarieren
Lassen
x: Zahl?[].

Es ist nicht perfekt, aber ich denke, es ist gut genug für die meisten Anwendungen in der realen Welt.
Wenn Sie ein Purist sind, der ein Soundsystem möchte, dann sollten Sie
bestimmt
schau dir eine andere Sprache an, denn TS-Typ-System ist sowieso nicht stichhaltig.
Was
denkst du?


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


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

Und was meine Aussage mit Array-Längen angeht, könnten wir davon ausgehen, dass alle Array-Zugriffe in Grenzen sind und dass der Zugriff außerhalb der Grenzen undefiniert ist, es sei denn, dies wird explizit in der Schnittstelle angegeben. Das ist sehr ähnlich wie bei C/C++, und es würde sowohl eine bessere Typisierung als auch möglicherweise eine ganze Menge Compiler-Optimierungen ermöglichen, wenn sich jemand entschließt, einen Compiler eines Drittanbieters zu schreiben, der die Sprachspezifikation verwendet, aber nicht so besorgt ist passend zum emittieren.

Ich weiß, dass die Unterstützung von übereinstimmendem undefiniertem C/C++-Verhalten oberflächlich betrachtet sehr dumm klingt, aber ich denke, in diesem Fall könnte es sich lohnen. Es ist selten, etwas zu sehen, das tatsächlich _besser_ gemacht wird, indem ein Zugriff außerhalb der Grenzen erfolgt. 99,99% der Verwendungen, die ich dafür gesehen habe, sind nur extrem scharfe Code-Gerüche, die fast immer von Leuten gemacht werden, die mit JavaScript fast nicht vertraut sind.

(Die meisten dieser Leute haben meiner Erfahrung nach noch nicht einmal von CoffeeScript gehört, geschweige denn von TypeScript. Viele von ihnen kennen sogar die neue Version von JS, die gerade fertiggestellt und standardisiert wurde, ES2015.)

Gibt es hierzu eine Lösung?

Abgesehen von einem Typ, der keine NULL-Werte zulässt, scheint es immer noch nützlich zu sein, dass TypeScript fehlschlägt, wenn versucht wird, auf eine Eigenschaft einer Variablen zuzugreifen, die _sicher_ null ist.

var o = null;
console.log(o.x);

... sollte scheitern.

Durch das Typsystem sicherzustellen, dass der gesamte Array-Zugriff auf Grenzen überprüft wird, scheint ein Abdriften in den Bereich abhängiger Typen zu sein. Obwohl abhängige Typen ziemlich ordentlich sind, scheint dies ein viel größeres Feature zu sein als Typen, die keine NULL-Werte zulassen.

Es scheint, als gäbe es drei Optionen, wenn davon ausgegangen wird, dass die Begrenzungsprüfung für Arrays zur Kompilierzeit nicht erzwungen wird:

  1. Die Array-Indizierung (und jeder beliebige Zugriff auf Array-Elemente über den Index) wird als Rückgabewert eines NULL-fähigen Typs angesehen, selbst bei Arrays von Nicht-Null-fähigen Typen. Im Wesentlichen hat die [] "Methode" eine Typsignatur von T? . Wenn Sie wissen, dass Sie nur eine Indexierung mit Grenzüberprüfung durchführen, können Sie T? T! in Ihrem Anwendungscode in
  2. Die Array-Indizierung gibt genau denselben Typ (mit derselben NULL-Zulässigkeit) wie der generische Typparameter des Arrays zurück, und es wird davon ausgegangen, dass der gesamte Array-Zugriff von der Anwendung auf Grenzen überprüft wird. Der Zugriff außerhalb der Grenzen gibt undefined und wird nicht von der Typprüfung abgefangen.
  3. Die nukleare Option: Alle Arrays sind in der Sprache als NULL-fähig festcodiert, und Versuche, Arrays ohne NULL-Werte zu verwenden, schlagen bei der Typprüfung fehl.

All dies gilt auch für den indexbasierten Zugriff auf Eigenschaften von Objekten, zB object['property'] wobei object vom Typ { [ key: string ]: T! } .

Persönlich bevorzuge ich die erste Option, bei der die Indizierung in ein Array oder Objekt einen nullbaren Typ zurückgibt. Aber selbst die zweite Option scheint besser zu sein, als alles nullbar zu machen, was der aktuelle Stand der Dinge ist. Die nukleare Option ist eklig, aber ehrlich gesagt auch immer noch besser, als alles nullbar zu machen.

Es stellt sich die zweite Frage, ob Typen standardmäßig nicht-nullfähig oder standardmäßig nullfähig sein sollten. Es scheint in beiden Fällen nützlich zu sein, eine Syntax sowohl für explizit nullbare als auch explizit nicht nullbare Typen zu haben, um Generika zu handhaben; Stellen Sie sich beispielsweise eine get Methode für eine Containerklasse (zB eine Map) vor, die einen Wert annimmt und möglicherweise einen Typ zurückgibt, _auch wenn der Container nur Werte enthält, die keine NULL-Werte enthalten_:

class Container<K,V> {
  get(key: K): V? {
    // fetch from some internal data structure and return the value, if it exists
    // return null otherwise
  }
}

// only non-nullable values allowed in the container
const container = new Container<SomeKeyClass!, SomeValueClass!>();
const val: SomeValueClass!;
// ... later, we attempt to read from the container with a get() call
// even though only non-nullables are allowed in the container, the following should fail:
// get() explicitly returns null when the item can't be found
val = container.get(someKey);

In ähnlicher Weise möchten wir möglicherweise (dies ist ein weniger starkes Argument) sicherstellen, dass unsere Containerklasse bei Einfügungen nur Schlüssel akzeptiert, die nicht NULL sind, selbst wenn ein Schlüsseltyp verwendet wird, der NULL zulässt:

class Container<K, V> {
  insert(key: K!, val: V): void {
    // put the val in the data structure
    // the key must not be null here, even if K is elsewhere a nullable type
  }
}

const container = new Container<SomeKeyClass?, SomeValueClass>();
container.insert(null, new SomeValueClass()); // fails

Unabhängig davon, ob sich der Standard ändert, scheint es nützlich zu sein, eine explizite Syntax sowohl für Nullable-Typen als auch für Nicht-Nullable-Typen zu verwenden. Es sei denn, ich übersehe etwas?

An dem Punkt, an dem es eine Syntax für beide gibt, scheint die Standardeinstellung ein Compiler-Flag ähnlich wie --noImplicitAny . Persönlich würde ich dafür stimmen, dass die Standardeinstellung bis zu einer 2.0-Version gleich bleibt, aber beides scheint in Ordnung zu sein, solange es (zumindest vorübergehend) eine Fluchtluke gibt.

Ich würde die zweite Option vorziehen, da dies, obwohl sie zu undefiniertem Verhalten außerhalb der Grenzen des Zugriffs führen würde (im Bereich der TS-Eingabe), ich denke, dass dies ein guter Kompromiss dafür ist. Es kann die Geschwindigkeit erheblich erhöhen und ist einfacher zu handhaben. Wenn Sie wirklich erwarten, dass ein Zugriff außerhalb der Grenzen möglich ist, sollten Sie entweder explizit einen NULL-fähigen Typ verwenden oder das Ergebnis in einen NULL-fähigen Typ umwandeln (was immer möglich ist). Und im Allgemeinen, wenn das Array keine NULL-Werte zulässt, ist jeder Zugriff außerhalb der Grenzen fast immer ein Fehler, der irgendwann heftig ausbrechen sollte (es ist ein JS-Fehler).

Es erfordert ziemlich genau, dass der Programmierer genau sagt, was er erwartet. Es ist weniger typsicher, aber in diesem Fall denke ich, dass die Typsicherheit im Weg stehen kann.

Hier ist ein Vergleich mit jeder Option, am Beispiel einer Summationsfunktion (Primitive sind am problematischsten):

// Option 1
function sum(numbers: !number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += <!number> numbers[i]
  }
  return res
}

// Option 2
function sum(numbers: !number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += numbers[i]
  }
  return res
}

// Option 3
function sum(numbers: number[]) {
  let res = 0
  for (let i = 0; i < numbers.length; i++) {
    res += <!number> numbers[i]
  }
  return res
}

Ein weiteres Beispiel: eine map Funktion.

// Option 1
function map<T>(list: !T[], f: (value: !T, index: !number) => !T): !T[] {
  let res: !T[] = []
  for (let i = 0; i < list.length; i++) {
    res.push(f(<!T> list[i], i));
  }
  return res
}

// Option 2
function map<T>(list: !T[], f: (value: !T, index: !number) => !T): !T[] {
  let res: !T[] = []
  for (let i = 0; i < list.length; i++) {
    res.push(f(list[i], i));
  }
  return res
}

// Option 3
function map<T>(list: T[], f: (value: !T, index: !number) => !T): T[] {
  let res: T[] = []
  for (let i = 0; i < list.length; i++) {
    const entry = list[i]
    if (entry !== undefined) {
      res.push(f(<!T> entry, i));
    }
  }
  return res
}

Eine andere Frage: Was ist der Typ von entry in jedem von diesen? !string , ?string oder string ?

declare const regularStrings: string[];
declare const nullableStrings: ?string[];
declare const nonnullableStrings: !string[];

for (const entry of regularStrings) { /* ... */  }
for (const entry of nullableStrings) { /* ... */  }
for (const entry of nonnullableStrings) { /* ... */  }

Option drei war ein augenzwinkernder Vorschlag :stuck_out_tongue:

Re: Deine letzte Frage:

declare const regularStrings: string[];
declare const nullableStrings: string?[];
declare const nonNullableStrings: string![]; // fails typecheck in option three

for(const entry of regularStrings) {
  // option 1: entry is of type string?
  // option 2: depends on default nullability
}

for(const entry of nullableStrings) {
  // option 1 and 2: entry is of type string?
}

for(const entry of nonNullableStrings) {
  // option 1: entry is of type string?
  // option 2: entry is of type string!
}

In einigen Fällen – wenn Sie einen nicht nullbaren Typ zurückgeben möchten und ihn beispielsweise aus einem Array erhalten – müssen Sie eine zusätzliche Umwandlung mit Option 1 durchführen, vorausgesetzt, Sie haben an anderer Stelle garantiert, dass es keine undefinierten Werte gibt im Array (die Anforderung dieser Garantie ändert sich nicht, unabhängig davon, welcher Ansatz gewählt wird, nur die Notwendigkeit, as string! ). Persönlich bevorzuge ich es immer noch, weil es sowohl expliziter ist (Sie müssen angeben, wann Sie möglicherweise gefährliches Verhalten annehmen, als dass es implizit passiert) und konsistenter ist, wie die meisten Containerklassen funktionieren: zum Beispiel get einer Map get eindeutig Nullable-Typen zurück (sie gibt das Objekt zurück, wenn es unter dem Schlüssel vorhanden ist, oder null, wenn dies nicht der Fall ist), und wenn Map.prototype.get ein Nullable zurückgibt, sollte object['property'] wahrscheinlich tun das gleiche, da sie ähnliche Garantien für die Nichtigkeit bieten und ähnlich verwendet werden. Was Arrays als ungerade herauslässt, bei denen sich Nullreferenzfehler wieder einschleichen können und bei denen wahlfreier Zugriff vom Typsystem nicht nullbar sein darf.

Es gibt definitiv andere Ansätze; zum Beispiel verwendet Flow derzeit Option zwei , und zuletzt habe ich SoundScript überprüft,

Ich denke, der Leistungsaspekt ist an dieser Stelle äußerst theoretisch, da AFAIK TypeScript weiterhin das gleiche JS ausgibt, unabhängig von Casts für jede Auswahl hier und die zugrunde liegenden VMs weiterhin Arrays unter der Haube überprüfen. Von diesem Argument lasse ich mich also nicht allzu sehr beeindrucken. Die Frage dreht sich meiner Meinung nach hauptsächlich um Bequemlichkeit vs. Sicherheit; Für mich scheint der Sicherheitsgewinn hier den Komfort-Kompromiss wert zu sein. Natürlich ist beides eine Verbesserung gegenüber der NULL-Zulässigkeit aller Typen.

Ich stimme zu, dass der Performance-Teil hauptsächlich theoretisch ist, aber ich würde es trotzdem tun
wie die Bequemlichkeit der Annahme. Die meisten Arrays sind dicht und NULL-Zulässigkeit durch
default ist für boolesche und numerische Arrays nicht sinnvoll. Wenn es nicht ist
ein dichtes Array sein soll, sollte es als explizit nullable markiert werden, also
die Absicht ist klar.


TypeScript braucht wirklich eine Möglichkeit, Dinge zu behaupten, da Asserts oft
Wird verwendet, um die statische Typprüfung in anderen Sprachen zu unterstützen. Ich habe in der gesehen
V8-Codebasis ein UNREACHABLE(); Makro, das es ermöglicht, dass Annahmen a
etwas sicherer, das Programm zum Absturz bringen, wenn die Invariante verletzt wird. C++
hat static_assert für statische Assertionen, um die Typprüfung zu unterstützen.

Am Dienstag, 20. Oktober 2015 um 04:01 Uhr, Matt Baker [email protected]
schrieb:

Option drei war ein etwas ironischer Vorschlag [Bild:
:stuck_out_tongue:]

Re: Deine letzte Frage:

deklarieren Sie const regularStrings: string[];deklarieren Sie const nullableStrings: string?[];deklarieren Sie const nonNullableStrings: string![]; // schlägt die Typprüfung in Option 3 fehl
for(const Eintrag von regularStrings) {
// Option 1: Eintrag ist vom Typ String?
// Option 2: hängt von der standardmäßigen NULL-Zulässigkeit ab
}
for(const Eintrag von nullableStrings) {
// Option 1 und 2: Eintrag ist vom Typ String?
}
for(const Eintrag von nonNullableStrings) {
// Option 1: Eintrag ist vom Typ String?
// Option 2: Eintrag ist vom Typ String!
}

In einigen Fällen – wenn Sie einen Typ zurückgeben möchten, der keine NULL-Werte zulässt, und Sie sind
zum Beispiel aus einem Array – du musst einen zusätzlichen Cast machen
mit Option eins unter der Annahme, dass Sie woanders garantiert haben, dass es keine undefinierten gibt
Werte im Array (die Anforderung dieser Garantie ändert sich nicht)
egal welcher Ansatz gewählt wird, nur die Eingabe als String!).
Persönlich bevorzuge ich es immer noch, weil es sowohl expliziter ist (du musst
Geben Sie an, wann Sie möglicherweise gefährliches Verhalten zeigen, im Gegensatz dazu
geschieht implizit) und konsistenter zu den meisten Containerklassen
Arbeit: Zum Beispiel gibt die get-Funktion einer Map eindeutig Nullable-Typen zurück
(es gibt das Objekt zurück, wenn es unter dem Schlüssel existiert, oder null, wenn nicht),
und wenn Map.prototype.get ein nullable zurückgibt, dann object['property']
sollte wahrscheinlich das gleiche tun, da sie ähnliche Garantien über
Nullbarkeit und werden ähnlich verwendet. Was Arrays als ungerade hinterlässt
wo Nullreferenzfehler sich wieder einschleichen können und wo wahlfreier Zugriff ist
darf vom Typsystem nicht-nullierbar sein.

Es gibt definitiv andere Ansätze; zum Beispiel verwendet Flow derzeit
Option zwei http://flowtype.org/docs/nullable-types.html und letztes I
überprüft SoundScript hat spärliche Arrays in ihrer Spezifikation explizit illegal gemacht
https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf
(Nun, starker Modus/"SaneScript" macht sie illegal, und SoundScript ist ein
Obermenge der neuen Regeln), was das Problem einigermaßen umgeht
obwohl sie noch herausfinden müssen, wie sie mit der manuellen Länge umgehen können
Änderungen und mit Erstzuweisung.

Ich denke, der Leistungsaspekt ist an dieser Stelle extrem theoretisch,
da AFAIK TypeScript weiterhin dieselbe JS ausgibt, unabhängig davon
Casts für eine beliebige Auswahl hier und die zugrunde liegenden VMs werden fortgesetzt
Bounds-Checking-Arrays unter der Haube unabhängig. Also ich lasse mich nicht zu sehr davon beeinflussen
dieses Argument. Die Frage dreht sich für mich hauptsächlich um Bequemlichkeit vs.
Sicherheit; Für mich scheint der Sicherheitsgewinn hier den Komfort-Kompromiss wert zu sein. Von
Natürlich ist beides eine Verbesserung gegenüber der Tatsache, dass alle Typen NULL-fähig sind.


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

Isiah Wiesen

Sollen wir sie einfach als non-void Typen bezeichnen? Auf jeden Fall halte ich die explizite Definition von non-void mit T! oder !T für einen Fehler. Es ist schwer als Mensch zu lesen und auch schwer mit allen Fällen für den TypeScript-Compiler umzugehen.

Das Hauptproblem, das ich bei den standardmäßigen non-void Typen sehe, ist, dass es sich um eine bahnbrechende Änderung handelt. Nun, was, wenn wir einfach eine weitere statische Analyse hinzufügen, ähnlich wie bei Flow, die das Verhalten überhaupt nicht ändert, aber mehr Fehler auffängt? Dann können wir die meisten Fehler dieser Klasse jetzt abfangen, aber ändern Sie die Syntax nicht, und in Zukunft wird es viel einfacher sein, ein Compiler-Flag oder ein Standardverhalten einzuführen, das weniger eine wesentliche Änderung darstellt.

``` .ts
// Funktion kompiliert glücklich
Funktion len(x: string): Zahl {
Rückgabe x.Länge;
}

len("funktioniert"); // 5
len(null); // Fehler, keine Eigenschaftslänge von null

``` .ts
function len(x: string): number {
    if (x === null) {
        return -1;
    }
    return x.length;
}

len("works"); // 5
len(null); // null

Was hier wirklich passieren würde, ist, die Eingabedaten als non-void modellieren, aber void implizit hinzuzufügen, wenn es in der Funktion behandelt wird. Ebenso ist der Rückgabetyp non-void sei denn, er kann explizit null oder undefined

Wir können auch den Typ ?T oder T? hinzufügen, der die Null- (und/oder undefinierte) Prüfung vor der Verwendung erzwingt. Persönlich mag ich T? , aber es gibt einen Präzedenzfall, ?T mit Flow zu verwenden.

``` .ts
Funktion len(x: ?string): Zahl {
Rückgabe x.Länge; // Fehler: keine Längeneigenschaft für Typ ?String, Sie müssen einen Typwächter verwenden
}

One more example -- what about using function results?

``` .js
function len(x: string): number {
    return x.length;
}

function identity(f: string): string {
    return f;
}

function unknown(): string {
    if (Math.random() > 0.5) {
        return null;
    }
    return "maybe";
}

len("works"); // 5
len(null); // error, no property length of null

identity("works"); // "works": string
identity(null); // null: void
unknown(); // ?string

len(identity("works")); // 5
len(identity(null)); // error, no property length of null
len(unknown()); // error: no length property on type ?string, you must use a type guard

Unter der Haube passiert hier wirklich, dass TypeScript ableitet, ob ein Typ null sein kann oder nicht, indem es sieht, ob er null behandelt und möglicherweise einen null-Wert erhält.

Der einzige knifflige Teil hier ist die Schnittstelle zu Definitionsdateien. Ich denke, dies kann gelöst werden, indem standardmäßig angenommen wird, dass eine Definitionsdatei, die ein function(t: T) deklariert, eine Null-/ void Überprüfung durchführt, ähnlich wie im zweiten Beispiel. Dies bedeutet, dass diese Funktionen Nullwerte annehmen können, ohne dass der Compiler einen Fehler generiert.

Dies ermöglicht nun zwei Dinge:

  1. Schrittweise Übernahme der Syntax des Typs ?T , von der optionale Parameter bereits ausgetauscht würden.
  2. In Zukunft könnte ein Compiler-Flag --noImplicitVoid hinzugefügt werden, das Deklarationsdateien genauso behandelt wie kompilierte Codedateien. Dies wäre "umbrechend", aber wenn es in der Zukunft gemacht wird, werden die meisten Bibliotheken die bewährte Methode verwenden, ?T wenn der Typ void und T wenn es nicht geht. Es wäre auch ein Opt-in, sodass nur diejenigen betroffen wären, die sich dafür entscheiden, es zu verwenden. Dies könnte auch erfordern, dass die Syntax ?T verwendet wird, falls ein Objekt ungültig sein könnte.

Ich denke, dies ist ein realistischer Ansatz, da er viel mehr Sicherheit bietet, wenn der Quellcode in TypeScript verfügbar ist, diese kniffligen Probleme findet und gleichzeitig eine einfache, ziemlich intuitive und abwärtskompatible Integration mit Definitionsdateien ermöglicht wird.

Die Präfix-Variante ? wird auch in Closure Compiler-Annotationen, IIRC, verwendet.

Am Dienstag, den 17. November 2015, 13:37 Uhr schrieb Tom Jacques

Sollen wir sie einfach nicht-void-Typen nennen? Jedenfalls denke ich
Nicht-Void explizit mit T definieren! oder !T ist ein Fehler. Es ist schwierig
als Mensch zu lesen, und auch schwer zu handhaben in allen Fällen für
der TypeScript-Compiler.

Das Hauptproblem, das ich bei Nicht-Void-Typen sehe, ist standardmäßig, dass es ein
brechende Veränderung. Nun, was wäre, wenn wir nur eine weitere statische Analyse hinzufügen,
ähnlich wie Flow, das ändert das Verhalten überhaupt nicht, aber fängt an
mehr Fehler? Dann können wir jetzt die meisten Bugs dieser Klasse fangen, aber
Ändern Sie die Syntax nicht, und in Zukunft wird es viel einfacher sein,
ein Compiler-Flag oder ein Standardverhalten einführen, das weniger schädlich ist
Veränderung.

// Funktion kompiliert glücklichfunktion len(x: string): Zahl {
Rückgabe x.Länge;
}

len("funktioniert"); // 5
len(null); // Fehler, keine Eigenschaftslänge von null

Funktion len(x: string): Zahl {
wenn (x === null) {
zurück -1;
}
Rückgabe x.Länge;
}

len("funktioniert"); // 5
len(null); // Null

Was hier wirklich passieren würde, ist die Modellierung der Eingabedaten als Nicht-Void,
aber implizites Hinzufügen von void, wenn es in der Funktion behandelt wird. Ähnlich,
der Rückgabetyp ist nicht ungültig, es sei denn, er kann explizit null oder zurückgeben
nicht definiert

Wir können auch das ?T oder T? type, der die Null (und/oder
undefiniert) vor Gebrauch prüfen. Persönlich mag ich T?, aber es gibt Präzedenzfälle
um ?T mit Flow zu verwenden.

Funktion len(x: ?string): Zahl {
Rückgabe x.Länge; // Fehler: keine Längeneigenschaft für Typ ?String, Sie müssen einen Typwächter verwenden
}

Noch ein Beispiel – wie sieht es mit der Verwendung von Funktionsergebnissen aus?

Funktion len(x: string): Zahl {
Rückgabe x.Länge;
}
Funktionsidentität (f: Zeichenfolge): Zeichenfolge {
zurück f;
}
Funktion unbekannt(): Zeichenfolge {
if (Math.zufällig() > 0,5) {
null zurückgeben;
}
"vielleicht" zurückgeben;
}

len("funktioniert"); // 5
len(null); // Fehler, keine Eigenschaftslänge von null

Identität("funktioniert"); // "funktioniert": Zeichenfolge
Identität (null); // null: nichtig
Unbekannt(); // ?String

len(identity("funktioniert")); // 5
len(identität(null)); // Fehler, keine Eigenschaftslänge von null
len(unbekannt()); // Fehler: keine Längeneigenschaft für Typ ?String, Sie müssen einen Typwächter verwenden

Unter der Haube passiert hier wirklich, dass TypeScript
Ableiten, ob ein Typ null sein kann oder nicht, indem Sie sehen, ob er behandelt
null, und erhält einen möglicherweise null-Wert.

Der einzige knifflige Teil hier ist die Schnittstelle zu Definitionsdateien. ich
denken Sie, dass dies gelöst werden kann, indem Sie standardmäßig annehmen, dass a
Definitionsdatei, die eine Funktion (t: T) deklariert, führt eine Null- / Ungültigkeitsprüfung durch, viel
wie das zweite Beispiel. Dies bedeutet, dass diese Funktionen in der Lage sein werden
Nullwerte, ohne dass der Compiler einen Fehler generiert.

Dies ermöglicht nun zwei Dinge:

  1. Schrittweise Übernahme der ?T-Typ-Syntax, davon optional
    Parameter wären bereits vertauscht.
  2. In Zukunft könnte ein Compiler-Flag --noImplicitVoid, hinzugefügt werden
    was Deklarationsdateien genauso behandeln würde wie kompilierte Codedateien. Dies
    wäre "zerstörerisch", aber wenn es auf der anderen Seite gemacht würde, würde die Mehrheit der
    Bibliotheken werden die bewährte Methode der Verwendung von ?T übernehmen, wenn der Typ dies kann
    ungültig sein und T, wenn es nicht möglich ist. Es wäre auch Opt-in, also nur die
    wer sich für die Nutzung entscheidet, wäre betroffen.

Ich denke, dies ist ein realistischer Ansatz, da er die Sicherheit erheblich verbessert
Wenn die Quelle in TypeScript verfügbar ist, finden Sie diese kniffligen Probleme,
während es immer noch einfach, ziemlich intuitiv und abwärtskompatibel ist
Integration mit Definitionsdateien.


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

Guter Punkt. Es gibt auch viele Ähnlichkeiten mit den vorhandenen optionalen Parameterdeklarationen:

``` .ts
Schnittstelle mitOptionalProperty {
o?: Zeichenfolge
}
Schnittstelle mitVoidableProperty {
o: ?Schnur
}

Funktion withOptionalParam(o?: string) { }
Funktion withVoidableParam(o: ?string) { }
```

Tatsächlich verwenden sie prefix . Sie verwenden ? für explizite Nullable-Typen und ! für Nicht-Nullable-Typen, wobei Nullable der Standardwert ist.

Die Unterscheidung voidable vs. nullable ist sehr sinnvoll. :+1:

Ich habe das Gefühl, dass sich das im Kreis dreht.

Haben Sie oben alle gelesen, warum eine anfechtbare/nicht anfechtbare Definition das zugrunde liegende Problem nicht lösen wird?

@Griffork Ich habe jeden Kommentar gelesen. Ich gebe zu, dass das, was ich gesagt habe, eine Art Auffrischung / Kombination dessen ist, was andere gesagt haben, aber ich denke, es ist der realistischste Weg nach vorne. Ich weiß nicht, was Sie als das zugrunde liegende Problem sehen, aber für mich ist das zugrunde liegende Problem, dass null und undefined Teil jedes Typs sind und der Compiler derzeit keine Sicherheit beim Versuch gewährleistet um ein Argument vom Typ T . Nach meinem Beispiel:

``` .ts
Funktion len(x: string): Zahl {
Rückgabe x.Länge;
}

len("funktioniert");
// Kein Fehler -- 5

len(null);
// Compiler erlaubt dies, sollte aber hier mit etwas wie fehlern
// Fehler: keine Eigenschaft 'Länge' von null

len(undefiniert);
// Compiler erlaubt dies, sollte aber hier mit etwas wie fehlern
// Fehler: keine Eigenschaft 'Länge' von undefined
```

So sehe ich das grundlegende Problem der _Sprache_ -- den Mangel an Sicherheit. Vieles davon kann behoben werden, indem man sich den Argumentfluss in Funktionen mit statischer und Typanalyse ansieht und einen Fehler ausgibt, wenn etwas unsicher gemacht werden könnte. Es muss überhaupt keinen ?T Typ geben, wenn der Code verfügbar ist, da diese Analyse durchgeführt werden kann (wenn auch nicht in jedem Fall immer absolut genau). Der Grund für das Hinzufügen eines ?T Typs liegt darin, dass er die Sicherheitsprüfung erzwingt und die Absicht des Programmierers sehr deutlich macht.

Das Problem bei der _Implementation_ ist die Abwärtskompatibilität. Wenn das TS-Team morgen eine Änderung veröffentlichen würde, bei der jetzt ein Typ T standardmäßig nicht-void ist, würde er bestehenden Code zerstören, der derzeit void-Eingaben mit diesen Typsignaturen akzeptiert und verarbeitet. TS-Teammitglieder haben in genau dieser Ausgabe erklärt, dass sie nicht bereit sind, eine so große Veränderung vorzunehmen. Sie sind bereit, einige Dinge zu zerstören, wenn die Wirkung klein genug und der Nutzen groß genug ist, aber dies hätte eine zu große Wirkung.

Mein Vorschlag besteht aus zwei separaten Teilen, von denen einer meiner Meinung nach eine großartige Ergänzung der Sprache wäre, die nichts an der bestehenden Syntax/Semantik ändert, außer um echte echte Fehler zu finden, und der andere ist eine Möglichkeit, die Auswirkungen der Breaking Change, um in Zukunft die stärkeren Garantien von Nicht-Void-Typen zu erhalten. Es ist immer noch eine bahnbrechende Veränderung, aber von hoffentlich akzeptabler Größe und Art.

Teil eins:

  • Fügen Sie eine Analyse hinzu, um zu identifizieren, wann Null-/Undefiniert-/Void-Typen an Funktionen übergeben werden könnten, die sie nicht verarbeiten (funktioniert nur, wenn TS-Code vorhanden ist, nicht in Definitionsdateien).
  • Fügen Sie den Typ ?T der eine ungültige Prüfung erzwingt, bevor das Argument verwendet wird. Dies ist wirklich nur syntaktischer Zucker für einen Optionstyp auf Sprachebene.
  • Diese beiden Funktionen können unabhängig voneinander implementiert werden, da jede ihre eigenen Vorzüge hat

Zweiter Teil:

  • Warten. Später, nachdem Teil 1 eingeführt wurde und die Community und die Benutzer von TS Zeit hatten, diese Funktionen zu nutzen, wird der Standard T wenn der Typ nicht null ist, und ?T wenn der Typ null sein könnte. Dies ist nicht garantiert, aber ich denke, dies wäre eine klare, offensichtliche Best Practice.
  • Fügen Sie als separate Funktion eine Compileroption --noImplicitVoid die erfordert, dass Typen ?T wenn sie void sein können. Dies ist nur der Compiler, der die bereits vorhandene Best-Practice durchsetzt. Wenn es eine Definitionsdatei gibt, die nicht der Best Practice entspricht, wäre sie falsch, aber aus diesem Grund handelt es sich um eine Opt-in-Datei.
  • Wenn Sie wirklich streng sein wollten, könnte das Flag Argumente akzeptieren, die angeben, auf welche Verzeichnisse/Dateien es angewendet werden soll. Dann könnten Sie die Änderung nur auf Ihren Code anwenden und node_modules .

Ich denke, dies ist die realistischste Option, da Teil 1 auch ohne den zweiten Teil gemacht werden kann. Es ist immer noch eine gute Funktion, die dieses Problem weitgehend mildern wird. Sicher ist es nicht perfekt, aber ich nehme gut genug, wenn es bedeutet, dass es passieren kann. Es lässt auch in Zukunft die Option für echte Nicht-Null-Typen in der Tabelle. Ein Problem besteht derzeit darin, dass es umso wichtiger ist, es zu beheben, je länger dieses Problem besteht, da in TS mehr Code geschrieben wird. Mit Teil Eins sollte es im schlimmsten Fall dies drastisch verlangsamen und bestenfalls die Auswirkungen im Laufe der Zeit reduzieren.

@tejacques Ich für

@tejacques - FWIW, ich stimme Ihrer Einschätzung und Ihrem Vorschlag voll und ganz zu. Hoffen wir, dass das TS-Team zustimmt :)

Eigentlich zwei Dinge:

Erstens, ich bin mir nicht sicher, ob die Flow-ähnliche Analyse von Teil Eins notwendig ist. Obwohl es sehr cool und nützlich ist, würde ich es sicherlich nicht wollen, dass es voidable/ ?T Typen hält, die im aktuellen Design der Sprache viel praktikabler erscheinen und einen viel längerfristigen Wert bieten.

Ich würde --noImplicitVoid etwas anders formulieren - sagen wir, es verbietet die Zuweisung von null und undefined zu nicht löschbaren (d. h. Standard) Typen, anstatt "Typen zu sein". ?T wenn sie ungültig werden können". Ich bin mir ziemlich sicher, dass wir dasselbe meinen, nur Semantik; konzentriert sich eher auf die Verwendung als auf die Definition, was, wenn ich verstehe, wie TS funktioniert, die einzigen Dinge sind, die es tatsächlich durchsetzen kann.

Und mir ist gerade etwas eingefallen: Wir hätten dann vier Ebenen der Voidabilität (das gilt auch für Flow, das meiner Meinung nach einen großen Teil dieses Gesprächs inspiriert):

interface Foo {
  w: string;
  x?: string;
  y: ?string;
  z?: ?string;
}

Unter --noImplicitVoid kann w nur eine gültige Zeichenfolge sein. Dies ist ein großer Gewinn für die Typensicherheit. Auf Wiedersehen, Milliarden-Dollar-Fehler! Ohne --noImplicitVoid besteht die einzige Einschränkung natürlich darin, dass es angegeben werden muss, aber es kann null oder undefiniert sein. Dies ist ein ziemlich gefährliches Verhalten der Sprache, denke ich, weil es so aussieht, als würde es mehr garantieren, als es wirklich ist.

x ist unter den aktuellen Einstellungen völlig nachsichtig. Es kann eine Zeichenfolge, null, undefiniert sein und muss nicht einmal in Objekten vorhanden sein, die es implementieren. Unter --noImplicitVoid werden die Dinge etwas komplexer ... Sie definieren es vielleicht nicht, aber wenn Sie etwas darauf _tun_, kann es dann nicht ungültig sein? Ich denke, Flow geht damit so um, dass Sie x auf undefined (die Nicht-Existenz imitieren), aber nicht null . Dies könnte jedoch für TypeScript etwas zu eigensinnig sein.

Außerdem wäre es logisch sinnvoll, vor der Verwendung eine void-Prüfung von x zu verlangen, aber das wäre wiederum eine bahnbrechende Änderung. Könnte dieses Verhalten Teil des --noImplicitVoid Flags sein?

y kann auf jeden beliebigen String oder void-Wert gesetzt werden, aber _muss_ in irgendeiner Form vorhanden sein, auch wenn void. Und natürlich erfordert der Zugriff darauf einen Ungültigkeitscheck. Dieses Verhalten mag etwas überraschend sein. Sollten wir in Betracht ziehen, es _nicht_ so zu setzen, wie es auf undefined ? Wenn ja, was würde es von x , außer dass ein ungültiger Scheck erforderlich ist?

Und schließlich muss z nicht angegeben werden, kann auf absolut alles gesetzt werden (nun, außer natürlich auf einen Nicht-String) und erfordert (aus gutem Grund) eine Prüfung auf void, bevor darauf zugegriffen wird. Hier macht alles Sinn!

Es gibt eine kleine Überschneidung zwischen x und y , und ich vermute, dass x irgendwann von der Community eingestellt werden würde und das z Formular aus Gründen der maximalen Sicherheit bevorzugen würde .

@tejacques

Es muss überhaupt kein ?T-Typ vorhanden sein, wenn der Code verfügbar ist, da diese Analyse durchgeführt werden kann (wenn auch nicht in jedem Fall immer absolut genau).

Diese Aussage ist falsch. Viele Aspekte der NULL-Zulässigkeit können nicht berücksichtigt werden, selbst wenn der vollständige Quellcode verfügbar ist.

Zum Beispiel: Beim Aufrufen einer Schnittstelle (statisch) können Sie nicht wissen, ob die Übergabe von Null in Ordnung ist oder nicht, wenn die Schnittstelle nicht entsprechend annotiert ist. In einem strukturellen Typsystem wie TS ist es nicht einmal einfach zu erkennen, welche Objekte Implementierungen der Schnittstelle sind und welche nicht. Im Allgemeinen ist es Ihnen egal, aber wenn Sie die NULL-Zulässigkeit aus dem Quellcode ableiten möchten, würden Sie es tun.

Ein weiteres Beispiel: Wenn ich ein Array list und eine Funktion unsafe(x) deren Quellcode zeigt, dass sie kein null Argument akzeptiert, kann der Compiler nicht sagen, ob das Zeile sicher ist oder nicht: list.filter(unsafe) . Und tatsächlich ist dies nicht möglich, es sei denn, Sie können statisch wissen, was alle möglichen Inhalte von list sein werden.

Andere Fälle sind mit Vererbung und mehr verbunden.

Ich sage nicht, dass ein Codeanalysetool, das eine eklatante Verletzung von Nullverträgen anzeigt, keinen Wert hat (das tut es). Ich weise nur darauf hin, dass es IMHO ein Fehler ist, die Nützlichkeit von Null-Annotationen herunterzuspielen, wenn Quellcode verfügbar ist.

Ich hatte irgendwo in dieser Diskussion gesagt, dass ich denke, dass der Rückschluss auf NULL-Zulässigkeit in vielen einfachen Fällen dazu beitragen könnte, die Abwärtskompatibilität zu verringern. Aber es kann Quellanmerkungen nicht vollständig ersetzen.

@tejacques mein void===null :( Ich beschuldige nur das Aufwachen).
Danke für den zusätzlichen Beitrag, er machte für mich viel mehr Sinn als dein ursprünglicher Beitrag. Die Idee gefällt mir eigentlich ganz gut.

Ich werde euch drei separat antworten, weil es ein unleserlicher Blob ist, es in einen Beitrag zu schreiben.

@Griffork Kein Problem. Manchmal ist es schwierig, alles richtig durch Text zu vermitteln und ich denke, es war trotzdem eine Klärung wert.

@dallonf Deine Umformulierung ist genau das, was ich meine - wir sind auf derselben Seite.

Ich denke, der Unterschied zwischen x?: T und y: ?T in den Tooltips/Funktionsverwendung und in der Eingabe und dem verwendeten Schutz.

Die Angabe als optionales Argument ändert die Tooltips/Funktionsverwendung, sodass klar ist, dass es optional ist:
a?: T muss nicht als Argument im Funktionsaufruf übergeben werden, kann leer bleiben
Wenn eine Deklaration kein ?: hat, muss es als Argument im Funktionsaufruf übergeben werden und darf nicht leer bleiben

w: T ist ein erforderliches Nicht- void Argument (mit --noImplicitVoid )
benötigt keine Wache

x?: T ist ein optionales Argument, der Typ ist also wirklich T | undefined
erfordert einen if (typeof x !== 'undefined') Wächter.
Beachten Sie die dreifache Glyphe !== für die genaue Überprüfung von undefined .

y: ?T ist ein erforderliches Argument und der Typ ist wirklich T | void
erfordert einen if (y == null) Wächter.
Beachten Sie die doppelte Glyphe == die sowohl mit null als auch mit undefined übereinstimmt, dh void

z?: ?T ist ein optionales Argument, und der Typ ist wirklich T | undefined | void was T | void
erfordert eine if (z == null) Wache.
Beachten Sie erneut die doppelte Glyphe == die sowohl mit null als auch mit undefined übereinstimmt, dh void

Wie bei allen optionalen Argumenten können keine erforderlichen Argumente auf optionale folgen. Das wäre also der Unterschied. Sie könnten jetzt auch einfach einen null Wächter für das optionale Argument ausführen, und das würde auch funktionieren, aber ein wesentlicher Unterschied besteht darin, dass Sie den null Wert nicht an die Funktion übergeben könnten, wenn Sie nannte es; Sie könnten jedoch undefined .

Ich denke, all dies hat tatsächlich einen Platz, sodass die aktuelle optionale Argumentsyntax nicht unbedingt veraltet ist, aber ich stimme zu, dass die Form z die sicherste ist.

Edit: Wortlaut aktualisiert und einige Tippfehler behoben.

@jods4 Ich stimme so ziemlich allem zu, was du gesagt hast. Ich versuche nicht , die Bedeutung der nicht zu verharmlosen void Typisierung. Ich versuche nur, es eine Phase nach der anderen zu pushen. Wenn das TS-Team es später nicht kann, sind wir zumindest besser dran, und wenn sie es später schaffen, nachdem sie weitere Überprüfungen und ?T , ist die Mission erfüllt.

Ich denke, dass der Fall mit Arrays wirklich sehr knifflig ist. Du könntest immer so etwas tun:

``` .ts
Funktion numToString(x: Zahl) {
x.toString() zurückgeben;
}
var nums: number[] = Array(100);
numToString(nums[0]); // Du bist am Arsch!

You can try to do something specifically for uninitialized arrays, like typing the `Array` function as `Array<?T>` / `?T[]` and upgrading it to `T[]` after a for-loop initializing it, but I agree that you can't catch everything. That said, that's already a problem anyway, and arrays typically don't even send uninitialized values to `map`/`filter`/`forEach`.

Here's an example -- the output is the same on Node/Chrome/IE/FF/Safari.

``` .ts
function timesTwo(x: number) {
    return x * 2;
}
function all(x) {
    return true;
}
var nums: number[] = Array(100);
nums.map(timesTwo);
// [undefined x 100]
nums.filter(all);
// []
nums.forEach(function(x) { console.log(x); })
// No output

Das hilft Ihnen wirklich nicht viel, da es unerwartet ist, aber es ist heute kein Fehler in echtem JavaScript.

Das einzige, was ich noch betonen möchte, ist, dass man auch mit Schnittstellen Fortschritte machen kann, es ist nur viel mehr Arbeit und Aufwand über die statische Analyse als über das Typsystem, aber es ist nicht unähnlich dem, was jetzt schon passiert.

Hier ist ein Beispiel. Nehmen wir an, --noImplicitVoid ist deaktiviert

``` .ts
Schnittstelle ITransform{
(x: T): U;
}

Schnittstelle IHaveName {
Name: Zeichenfolge;
}

Funktion transformieren(x: T, fn: ITransform) {
fn(x) zurückgeben;
}

Variablenname = {
Name: "Foo"
};

var falscher Name = {
Name: 1234
};

var namedNull = {
Name: null
};

var someFun = (x: IHaveName) => x.name;
var someFunHandlesVoid = (x: IHaveName) => {
if (x != null && x.name != null) {
x.name zurückgeben;
}
"Kein Name" zurückgeben;
};

All of the above code compiles just fine -- no issues. Now let's try using it

``` .ts
someFun(named);
// "Foo"
someFun(wrongName);
// error TS2345: Argument of type '{ name: number; }' is not assignable to parameter
// of type 'IHaveName'.
//   Types of property 'name' are incompatible.
//     Type 'number' is not assignable to type 'string'.
someFun(null);
// Not currently an error, but would be something like this:
// error TS#: Argument of type 'null' is not assignale to parameter of type 'IHaveName'.
someFun(namedNull);
// Not currently an error, but would be something like this:
// error TS#: Argument of type '{ name: null; }' is not assignable to parameter of
// type 'IHaveName'.
//   Types of property 'name' are incompatible.
//     Type 'null' is not assignable to type 'string'.

someFunHandlesVoid(named);
// "Foo"
someFunHandlesVoid(wrongName);
// error TS2345: Argument of type '{ name: number; }' is not assignable to parameter
// of type 'IHaveName'.
someFunHandlesVoid(null);
// "No Name"
someFunHandlesVoid(namedNull);
// "No Name"

transform(named, someFun);
// "Foo"
transform(wrongName, someFun);
// error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: number; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
//     Types of property 'name' are incompatible.
//       Type 'string' is not assignable to type 'number'.
transform(null, someFun);
// Not currently an error, but would be something like this:
// error TS#: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate 'null' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
transform(namedNull, someFun);
// Not currently an error, but would be something like this:
// error TS#: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: null; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
//     Types of property 'name' are incompatible.
//       Type 'string' is not assignable to type 'null'.

transform(named, someFunHandlesVoid);
// "Foo"
transform(wrongName, someFunHandlesVoid);
// error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage.
// Consider specifying the type arguments explicitly.
//   Type argument candidate '{ name: number; }' is not a valid type argument because it
//   is not a supertype of candidate 'IHaveName'.
transform(null, someFunHandlesVoid);
// "No Name"
transform(namedNull, someFunHandlesVoid);
// "No Name"

Sie haben Recht, Sie können nicht alles fangen, aber Sie können viele Dinge fangen.

Abschließende Anmerkung – wie sollte sich das obige Verhalten verhalten, wenn --noImplicitVoid ist?

Jetzt werden someFun und someFunHandlesVoid beide gleich typgeprüft und erzeugen dieselben Fehlermeldungen, die someFun erzeugt hat. Obwohl someFunHandlesVoid mit void behandelt, ist der Aufruf mit null oder undefined ein Fehler, da die Signatur besagt, dass es nicht void braucht. Es müsste als (x: ?IHaveName) : string eingegeben werden, um null oder undefined zu akzeptieren. Wenn wir den Typ ändern, funktioniert es weiterhin wie zuvor.

Dies ist der Teil, der eine bahnbrechende Änderung darstellt, aber alles, was wir tun müssen, um es zu beheben, war ein einzelnes Zeichen ? zur Typsignatur hinzuzufügen. Wir können sogar ein weiteres Flag --warnImplicitVoid , das dasselbe wie eine Warnung tut, damit wir langsam übergehen können.

Ich fühle mich wie ein totaler Idiot dafür, aber ich werde noch einen Post machen.

An dieser Stelle bin ich mir nicht sicher, was ich tun soll. Gibt es eine bessere Idee? Sollten wir:

  • Diskutieren Sie weiter / spezifizieren Sie, wie sich das verhalten soll?
  • daraus drei Vorschläge für neue Funktionen machen?

    • Erweiterte Analyse

    • Vielleicht/Optionstyp ?T

    • --noImplicitVoid Compiler-Option

  • Mitglieder des TypeScript-Teams um Eingabe bitten?

Ich neige zu neuen Vorschlägen und kontinuierlichen Diskussionen, da es fast unmenschlich ist, das TypeScript-Team zu bitten, diesen Thread nachzuholen, wenn man bedenkt, wie lange er dauert.

@tejacques

  • Sie vermissen ein typeof im Triple-Equals-Beispiel für Dallonf.
  • Im Beispiel zu jods4 scheinen Ihnen einige ? zu fehlen.

So sehr ich denke, dass das Zeug in diesem Thread bleiben sollte, denke ich, dass dieser Thread nicht mehr wirklich "beobachtet" wird (vielleicht eher wie ein gelegentlicher Blick). Das Erstellen einiger neuer Threads würde also definitiv die Zugkraft erhöhen.
Aber warten Sie ein paar Tage/eine Woche, bis die Leute ihren Kopf hochheben und zuerst Feedback geben. Sie möchten, dass Ihr Vorschlag ziemlich solide ist.

Bearbeiten: Erinnerter Markdown existiert.

Kommentare zu diesem Thread sind derzeit ziemlich sinnlos. Selbst wenn ein Vorschlag gemacht wird, den das TypeScript-Team für akzeptabel hält (ich habe dies oben im August versucht), werden sie ihn auf keinen Fall im Lärm finden.

Das Beste, was Sie hoffen können, ist, dass die Aufmerksamkeit für das TypeScript-Team ausreichend ist, um einen eigenen Vorschlag zu unterbreiten und diesen umzusetzen. Ansonsten vergiss es einfach und verwende Flow.

+1 für die Aufteilung, aber im Moment kann die Option --noImplicitVoid
warten, bis der Nullable-Typ implementiert ist.

Bisher haben wir uns meistens über die Syntax und Semantik von
Nullable Typen, also wenn jemand einen Vorschlag und eine Implementierung schreiben könnte
davon, das wäre golden. Ich habe einen Vorschlag aus einem ähnlichen Prozess
bezüglich Aufzählungen anderer Typen, aber ich hatte einfach keine Zeit dafür
aufgrund anderer Projekte umsetzen.

Am Mittwoch, den 18. November 2015, 21:24 Uhr schrieb Tom Jacques

Ich fühle mich wie ein totaler Idiot dafür, aber ich werde noch einen machen
Post.

An dieser Stelle bin ich mir nicht sicher, was ich tun soll. Gibt es eine bessere Idee?
Sollten wir:

  • Diskutieren Sie weiter / spezifizieren Sie, wie sich das verhalten soll?
  • daraus drei Vorschläge für neue Funktionen machen?

    • Erweiterte Analyse

    • Vielleicht/Optionstyp ?T

    • --noImplicitVoid Compileroption

  • Mitglieder des TypeScript-Teams um Eingabe bitten?

Ich neige zu neuen Vorschlägen und diskutiere dort seitdem weiter
Es ist fast unmenschlich, das TypeScript-Team zu bitten, diesen Thread nachzuholen
wenn man bedenkt, wie lange es ist.


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

+1 für --noImplicitNull Option (Nichtigkeits- und Nullzuweisung zulassen).

Ich habe versucht, dieses Problem mit einem speziellen Typ Op<A> = A | NullType zu mildern. Es scheint ziemlich gut zu funktionieren. Siehe hier .

+1 auch für _--noImplicitNull_ BITTE :+1:

+1 für --noImplicitNull

Soll das geschlossen werden?

@Gaelan Angesichts der Tatsache, dass #7140 zusammengeführt wurde, möchten Sie, wie von einigen Leuten hier vorgeschlagen, ein neues, dediziertes Problem für --noImplicitNull einreichen, dann ist dies wahrscheinlich jetzt sicher.

@isiahmeadows Dann wäre es wahrscheinlich besser, dies offen zu lassen.

Soll das geschlossen werden?

Wir denken, dass https://github.com/Microsoft/TypeScript/issues/2388 der Teil dieser Arbeit ist, der umbenannt wird. Aus diesem Grund haben wir diese Funktion noch nicht für vollständig erklärt.

Wenn Sie ein neues, dediziertes Problem für --noImplicitNull einreichen möchten, wie von einigen Leuten hier vorgeschlagen, dann ist dies wahrscheinlich jetzt sicher.

Ich bin mir nicht sicher, ob ich die angeforderte Semantik dieses neuen Flags verstehe. Ich würde empfehlen, eine neue Ausgabe mit einem klaren Vorschlag zu eröffnen.

@mhegazy Die früher in dieser Ausgabe für --noImplicitNull aufgestellte Idee war, dass alles explizit ?Type oder !Type . IMHO glaube ich nicht, dass es den Boilerplate wert ist, wenn es ein anderes Flag gibt, das standardmäßig non-nullable ableitet, dass IIRC bereits implementiert wurde, als nullable Typen selbst es waren.

Jetzt geschlossen, da #7140 und #8010 beide zusammengeführt wurden.

Es tut mir leid, wenn ich ein geschlossenes Problem kommentiere, aber ich kenne keinen besseren Ort, an dem ich nachfragen kann, und ich denke nicht, dass dies eine neue Ausgabe wert ist, wenn kein Interesse besteht.
Wäre es möglich, implizite Nullen auf Dateibasis zu behandeln?
Behandeln Sie zum Beispiel eine Reihe von td-Dateien mit noImplicitNull (weil sie von definitivtyped stammen und so konzipiert wurden), aber meine Quelle als implicitNull behandeln?
Würde das jemand nützlich finden?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen