Typescript: Suggestion : type non nullable

Créé le 22 juil. 2014  ·  358Commentaires  ·  Source: microsoft/TypeScript

Introduction de deux nouvelles syntaxes pour la déclaration de type basée sur 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

par défaut, les types sont nullables.

Deux nouveaux flags du compilateur :

  • inferNonNullableType au compilateur d'inférer un type non nullable :
var myString3 = 'hello world' // typeof myString is '!string', non-nullable
  • nonNullableTypeByDefault (je suppose qu'il pourrait y avoir un meilleur nom) :
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

Commentaire le plus utile

Désolé si je commente un problème clos mais je ne connais pas de meilleur endroit où demander et je ne pense pas que cela vaut la peine d'un nouveau numéro s'il n'y a pas d'intérêt.
Serait-il possible de gérer les valeurs nulles implicites par fichier ?
Par exemple, gérer un tas de fichiers td avec noImplicitNull (car ils proviennent de définitivement typés et ont été conçus de cette façon) mais gérer ma source comme impliciteNull ?
Est-ce que quelqu'un trouverait cela utile ?

Tous les 358 commentaires

Je suggère d'utiliser un type autre que string comme exemple car il est par nature nullable. :P
Je peux percevoir que les types non nullables sont problématiques puisque l'utilisateur et le compilateur de "!" s'attend à ce que le type soit toujours non nul, ce qui ne peut jamais être vraiment affirmé en JavaScript. Un utilisateur peut définir quelque chose comme ceci :

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

Et parce qu'il est défini comme non nul, tout peut être heureux pour le compilateur, mais alors quelqu'un d'autre qui l'utilise en javascript passe quelque chose de nul et de kaboom.

Cela dit, peut-être que la solution pourrait être que pour les méthodes destinées au public, cela pourrait automatiquement affirmer non null. Mais alors le compilateur pourrait également affirmer que vous ne pouvez pas avoir de propriétés publiques (ou privées d'ailleurs vraiment) qui peuvent avoir une déclaration !nonnull puisqu'elles ne peuvent pas être appliquées.

Cela peut aller plus loin dans la discussion des contrats de code pour que cela soit correctement appliqué.

Pardonnez mes critiques, je pense qu'il y a très peu de besoin de types non nullables si/dès que les types de données algébriques sont ici. La raison pour laquelle les gens utilisent null pour représenter une valeur manquante est qu'il n'y a pas de meilleur moyen de le faire en JavaScript et dans la plupart des langages POO. Les ADT d'imagerie sont donc déjà là. Ensuite, comme pour les anciennes bibliothèques écrites avant les non-nullables, les avoir ne rendra pas la vie meilleure. En ce qui concerne les nouvelles bibliothèques, avec les ADT en place, on peut modéliser très précisément ce qu'une valeur peut prendre en fonction de la spécification du domaine métier sans utiliser de valeurs nulles. Donc, je suppose que ce que je dis, c'est qu'ADT est un outil bien plus puissant pour résoudre le même problème.

Personnellement, je viens d'écrire une petite interface Maybe<T> et d'utiliser la discipline pour m'assurer que les variables de ce type ne sont jamais nulles.

Je suggère d'utiliser un type autre que string comme exemple car il est par nature nullable. :P
Je peux percevoir que les types non nullables sont problématiques puisque l'utilisateur et le compilateur de "!" s'attend à ce que le type soit toujours non nul, ce qui ne peut jamais être vraiment affirmé en JavaScript. Un utilisateur peut définir quelque chose comme ceci :

function(myNonNull:!myClass):void {
monNonNull.foo();
}
Et parce qu'il est défini comme non nul, tout peut être heureux pour le compilateur, mais alors quelqu'un d'autre qui l'utilise en javascript passe quelque chose de nul et de kaboom.

Je ne comprends pas vraiment que vous puissiez également définir :

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

et si quelqu'un passe un int à cette fonction, cela se terminera par une erreur également, un avantage de typescript est de déléguer au compilateur passer des choses que vous vérifieriez manuellement en javascript, en ayant une autre vérification pour nullable/non -le type nullable me semble raisonnable. Soit dit en passant, SaferTypeScript et ClosureCompiler font déjà ce genre de vérification.

Avec les types d'union, nous pourrions avoir une spécification assez simple pour cela.
Disons que nous avons maintenant un type de base 'null', nous pouvons avoir un mode 'plus strict' où 'null' et 'undefined' n'est compatible avec aucun type, donc si nous voulons exprimer une valeur nullable nous ferions :

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

Avec le "mode strict" activé, le script doit vérifier que chaque variable non nullable est initialisée, également par défaut les paramètres facultatifs sont nullables.

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 d'après ce que Facebook a montré de Flow qui utilise la syntaxe TypeScript mais avec des types non nullables par défaut, ils prennent en charge un raccourci pour (null | T) comme dans votre message d'origine - je pense que c'était ?T ou T? .

var myString: string; // error

Cela pourrait potentiellement être assez ennuyeux dans le cas où vous souhaitez initialiser une variable de manière conditionnelle, par exemple :

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

Dans Rust par exemple, c'est bien tant que le compilateur peut voir que myString sera initialisé avant d'être utilisé, mais l'inférence de TypeScript ne le prend pas en charge pour le moment.

Honnêtement, faire quelque chose comme var myString = '' au lieu de var myString: string ne me dérange pas tellement, mais c'est sûr que ce genre de règle est toujours possible.

@fdecampredon +1 pour cela - J'aime beaucoup l'idée. Pour les bases de code 100% JavaScript, ce serait une contrainte utile uniquement au moment de la compilation. (D'après ce que je comprends de votre proposition, il n'y a aucune intention que le code généré applique cela ?)

En ce qui concerne le raccourci pour (null | string) bien sûr que ?string est bien.
Et bien sûr @johnnyreilly ce n'est qu'une vérification de l'heure de compilation

Les types de somme font des types non nullables par défaut une possibilité très intéressante. Les propriétés de sécurité de non-nullable par défaut ne peuvent pas être surestimées. Les types de somme plus la "déstructuration if/typeof" prévue (je ne sais pas comment cela devrait être appelé) permettent même d'intégrer en toute sécurité des API nullables et non nullables.

Cependant, rendre les types non nullables par défaut est un changement majeur, qui nécessiterait de modifier presque tous les fichiers de définition de type tiers existants. Bien que je sois à 100% pour le changement radical, personne n'est en mesure de mettre à jour les définitions de type qui existent dans la nature.

C'est bien qu'un grand consensus de ces définitions soit collecté dans le référentiel DefinitelyTyped, mais j'ai toujours des inquiétudes pratiques concernant cette fonctionnalité.

@samwgoldman l'idée est d'avoir des types non nullables uniquement sous un drapeau de compilateur spécial comme nonImplicitAny ce drapeau pourrait être nommé strict ou nonNullableType . Il n'y aurait donc pas de changements de rupture.

@fdecampredon Qu'en est-il des définitions de type pour les bibliothèques non TypeScript, comme celles de DefinitelyTyped ? Ces définitions ne sont pas vérifiées par le compilateur, donc tout code tiers qui pourrait retourner null devra être ré-annoté afin de fonctionner correctement.

Je peux imaginer une définition de type pour une fonction qui est actuellement annotée comme "chaîne de retour", mais renvoie parfois null. Si je dépendais de cette fonction dans mon code nonNullableType 'ed, le compilateur ne se plaint pas (comment pourrait-il?) Et mon code n'est plus null-safe.

À moins que quelque chose ne me manque, je ne pense pas que ce soit une fonctionnalité qui puisse être activée et désactivée avec un indicateur. Il me semble qu'il s'agit d'un changement sémantique du tout ou rien pour assurer l'interopérabilité. Je serais heureux d'avoir tort, cependant, car je pense qu'une fonctionnalité de commutation de drapeau est plus susceptible de se produire.

Soit dit en passant, il n'y a pas encore beaucoup d'informations disponibles sur le compilateur Flow de Facebook, mais d'après l'enregistrement vidéo de la présentation, il semble qu'ils soient allés avec non-nullable par défaut. Si c'est le cas, au moins il y a une certaine priorité ici.

Ok, supposons qu'il existe un raccourci ? type pour type | null | undefined .

@fdecampredon Qu'en est-il des définitions de type pour les bibliothèques non TypeScript, comme celles de DefinitelyTyped ? Ces définitions ne sont pas vérifiées par le compilateur, donc tout code tiers qui pourrait retourner null devra être ré-annoté afin de fonctionner correctement.

Je peux imaginer une définition de type pour une fonction qui est actuellement annotée comme "chaîne de retour", mais renvoie parfois null. Si je dépendais de cette fonction dans mon code non NullableType, le compilateur ne se plaint pas (comment le pourrait-il ?) et mon code n'est plus null-safe.

Je ne vois pas le problème, bien sûr que certains fichiers de définition ne seront pas valides avec le mode nonNullableType , mais la plupart du temps une bonne bibliothèque évite de retourner null ou undefined donc la définition sera toujours correcte dans la majorité des cas.
Quoi qu'il en soit, je peux rarement choisir une définition DefinitelyTyped sans avoir à la vérifier/la modifier, vous aurez juste un peu de travail supplémentaire pour ajouter un préfixe ? avec certaines définitions.

À moins que quelque chose ne me manque, je ne pense pas que ce soit une fonctionnalité qui puisse être activée et désactivée avec un indicateur. Il me semble qu'il s'agit d'un changement sémantique du tout ou rien pour assurer l'interopérabilité. Je serais heureux d'avoir tort, cependant, car je pense qu'une fonctionnalité de commutation de drapeau est plus susceptible de se produire.

Je ne vois pas pourquoi nous ne pourrions pas avoir une fonctionnalité de changement de drapeau, les règles seraient simples :

  • en mode normal ? string équivaut à string et null ou undefined sont assignables à tous les types
  • en mode nonNullableType ? string équivaut à string | null | undefined et null ou undefined ne sont pas attribuables à un autre type que null ou undefined

Où est l'incompatibilité avec une fonction à drapeau commuté ?

Les drapeaux qui modifient la sémantique d'une langue sont une chose dangereuse. Un problème est que les effets sont potentiellement très non locaux :

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

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

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

Il est important que quelqu'un qui regarde un morceau de code puisse "suivre" le système de types et comprendre les inférences qui sont faites. Si nous commençons à avoir un tas de drapeaux qui changent les règles de la langue, cela devient impossible.

La seule chose sûre à faire est de garder la même sémantique d'assignabilité et de changer ce qui est une erreur par rapport à ce qui ne dépend pas d'un indicateur, un peu comme le fonctionne noImplicitAny aujourd'hui.

Je sais que cela casserait la rétro-compatibilité, je comprends le point de vue de @RyanCavanaugh , mais après avoir goûté qu'avec flowtype, c'est vraiment une fonctionnalité inestimable, j'espère que cela finira par faire partie du tapuscrit

En plus du commentaire de RyanCavanaugh --> D'après ce que j'ai lu quelque part, la spécification / proposition ES7 mentionne l'utilisation de la surcharge de fonction (même nom de fonction mais type de données de paramètre d'entrée différent). C'est une fonctionnalité très nécessaire pour Javascript.

A partir de la doc de flux :

Flow considère null comme une valeur distincte qui ne fait partie d'aucun autre type

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

Tout type T peut être amené à inclure null (et la valeur associée indéfinie) en écrivant ?T

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

[Flow] comprend les effets de certains tests de type dynamique

(c'est-à-dire dans le jargon TS comprend les gardes de type)

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

Limites

  • Les contrôles sur les propriétés des objets sont limités en raison de la possibilité d'alias :

En plus de pouvoir ajuster les types de variables locales, Flow peut parfois aussi ajuster les types de propriétés d'objets, notamment lorsqu'il n'y a pas d'opérations intermédiaires entre un contrôle et une utilisation. En général, cependant, l'alias d'objets limite la portée de cette forme de raisonnement, car une vérification sur une propriété d'objet peut être invalidée par une écriture sur cette propriété via un alias, et il est difficile pour une analyse statique de suivre précisément les alias.

  • Les vérifications de type garde peuvent être redondantes pour les propriétés d'objet.

[N] ne vous attendez pas à ce qu'un champ nullable soit reconnu comme non null dans certaines méthodes, car une vérification null est effectuée dans une autre méthode de votre code, même lorsqu'il est clair pour vous que la vérification null est suffisante pour la sécurité à temps d'exécution (par exemple, parce que vous savez que les appels à la première méthode suivent toujours les appels à la dernière méthode).

  • undefined n'est pas coché.

Les valeurs non définies, tout comme null, peuvent également causer des problèmes. Malheureusement, les valeurs non définies sont omniprésentes dans JavaScript et il est difficile de les éviter sans affecter gravement la convivialité du langage. Par exemple, les tableaux peuvent avoir des trous pour les éléments ; les propriétés de l'objet peuvent être ajoutées et supprimées dynamiquement. Flow fait un compromis dans ce cas : il détecte les variables locales non définies et les valeurs de retour, mais ignore la possibilité d'un indéfini résultant des accès aux propriétés de l'objet et aux éléments du tableau

Et si l'option était ajoutée en même temps lors de l'introduction du type null (et du point d'interrogation) ? La présence d'un type null dans un fichier forcerait le compilateur à passer en mode non nullable pour ce fichier même si l'indicateur n'est pas présent sur la ligne de commande. Ou est-ce un peu trop magique ?

@jbondc semble bien. Cependant, le problème avec cela est qu'il se retrouvera avec ! partout :p

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

Qu'est-ce que ça veut dire? Il n'y a pas de types statiques dans js. Donc, oui, les chaînes sont "nullables", mais n'oublions pas qu'elles sont aussi numérotables et objectables et fooables, etc. Toute valeur peut avoir n'importe quel type.

Ainsi, lors de la superposition d'un système de types statiques au-dessus de javascript, choisir si les types statiques sont nullables ou non n'est qu'une décision de conception. Il me semble que les types non nullables sont une meilleure valeur par défaut, car ce n'est généralement que dans des cas particuliers que vous souhaitez qu'une signature de fonction, par exemple, accepte une valeur null en plus du type spécifié.

Les directives telles que "use strict" qui provoquent des changements de portée de la sémantique font déjà partie du langage ; Je pense qu'il serait raisonnable d'avoir une directive "utiliser des types non nullables" dans TypeScript.

@metaweta Je ne pense pas que ce soit suffisant, par exemple que se passe-t-il si un _module non nul_ en consomme un nul :

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

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

data dans le module B est en fait nullable, mais puisque 'use nonnull' n'a pas été utilisé dans le module A doit-on signaler une erreur ?
Je ne vois pas de moyen de résoudre ce problème avec une fonctionnalité basée sur des directives.

Oui,

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

provoquerait une erreur. Au lieu de cela, vous devrez fournir une valeur par défaut lorsque getData() renvoie null :

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

@metaweta ok mais comment tu sais que c'est une erreur ? :)
le type de getData est toujours '() => string[]' traiteriez-vous automatiquement tout ce qui provient d'un 'module nullable' comme 'nullable' ?

Oui, exactement (à moins qu'un type du module nullable ne soit explicitement marqué autrement).

Il semble que vous vouliez maintenant un indicateur par fichier qui dicte si ce fichier est par défaut nullable ou non.

Personnellement, je pense qu'il est un peu tard pour introduire ce changement, et @RyanCavanaugh a raison, le changement rendrait Typescript moins prévisible car vous ne seriez pas en mesure de déterminer ce qui se passait simplement en regardant un fichier.

Les projets démarrent-ils avec cet indicateur de compilateur activé ou désactivé par défaut ? Si quelqu'un travaille sur un projet nullable par défaut et crée/passe à un projet nullable par défaut, cela créera-t-il de la confusion ?
Je travaille actuellement avec No Implicit Any dans la plupart de mes projets, et chaque fois que je tombe sur un projet pour lequel cette option n'est pas activée, cela me prend par surprise.

Le non implicite est bon, mais en termes de drapeaux qui changent le comportement de la langue, je pense que cela devrait être la ligne. Pas plus que cela et les personnes qui travaillent sur plusieurs projets lancés par différentes personnes avec des règles différentes vont perdre beaucoup de productivité en raison de fausses hypothèses et de dérapages.

@RyanCavanaugh était préoccupé par la non-localité et les directives ont une portée lexicale. Vous ne pouvez pas être plus local à moins d'annoter chaque site. Je ne suis pas particulièrement favorable à la directive; Je faisais juste remarquer que l'option existe et qu'elle est au moins aussi raisonnable que "use strict" dans ES5. Je suis personnellement en faveur des types non nullables par défaut, mais pratiquement, il est trop tard pour cela. Compte tenu de ces contraintes, je suis favorable à l'utilisation ! en quelque sorte. La proposition de @jbondc vous permet de distinguer null de undefined ; étant donné que les backends Java continuent d'inciter les gens à utiliser les deux valeurs, cela me semble le plus utile.

Je suis désolé si je n'ai pas été clair, j'étais à la fois d'accord avec Ryan et en ajoutant mes propres préoccupations.

Honnêtement, si l'ajout de use not-null est le prix à payer pour éviter toutes les exceptions de pointeur nul, je le paierais sans aucun problème, considérant que null ou undefined comme assignable à n'importe quel type est la pire erreur ce tapuscrit fait à mon avis.

@jbondc Je n'ai pas utilisé 'use strict' et je fais donc quelques hypothèses, veuillez me corriger si mes hypothèses sont fausses :

Non null n'affecte pas la syntaxe que le programmeur écrit, mais les capacités du prochain programmeur qui essaie d'utiliser ce code (en supposant que le créateur et l'utilisateur sont des personnes distinctes).

Donc le code :

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

(taper sur un téléphone donc peut-être faux)
Est un code valide à la fois dans un projet normal et dans un projet non nul. La seule façon pour le codeur de savoir si le code fonctionne ou non est de regarder les arguments de la ligne de commande.

Au travail, nous avons un projet de test (qui inclut le prototypage de nouvelles fonctionnalités) et un projet principal (avec notre code actuel). Lorsque les prototypes sont prêts à être déplacés d'un projet à un autre (généralement avec de grands réfracteurs), il n'y aurait pas d'erreurs dans le code, mais des erreurs dans l'utilisation du code. Ce comportement est différent de no implicite any et utilise strict, ce qui entraînerait une erreur immédiate.

Maintenant, j'ai une bonne influence sur ces projets, je peux donc avertir les personnes en charge de ne pas utiliser cette nouvelle "fonctionnalité" car cela ne ferait pas gagner du temps, mais je n'ai pas cette capacité sur tous les projets à travail.
Si nous voulons activer cette fonctionnalité dans un seul projet, nous devons l'activer dans tous nos autres projets car nous avons une quantité très importante de partage de code et de migration de code entre les projets, et cette « fonctionnalité » nous causerait beaucoup de temps en arrière et de « réparation » des fonctions qui étaient déjà terminées.

À droite @jbondc. @Griffork : Désolé, je n'ai pas compris ce malentendu ; une directive est une expression de chaîne littérale qui apparaît comme la première ligne d'une production de programme ou d'une fonction et ses effets sont limités à cette production.

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

contre

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

Les types non nullables sont inutiles. Les types non nullables sont inutiles. Ils sont inutiles. Inutile! Vous ne vous en rendez pas compte, mais vous n'en avez pas vraiment besoin. Il n'y a que très peu de sens à se restreindre en proclamant qu'à partir de maintenant nous n'utiliserons plus les valeurs NULL. Comment représenteriez-vous une valeur manquante, par exemple dans une situation où vous essayez de trouver une sous-chaîne qui n'est pas là ? Ne pas pouvoir exprimer une valeur manquante (ce que NULL fait pour le moment) ne résoudra pas votre problème. Vous échangerez un monde dur avec des NULL partout contre un monde tout aussi dur sans aucune valeur manquante. Ce dont vous avez vraiment besoin s'appelle des types de données algébriques qui (parmi beaucoup d'autres choses intéressantes) ont la capacité de représenter une valeur manquante (ce que vous recherchez en premier lieu et ce qui est représenté par NULL dans le monde impératif). Je suis fortement contre l' ajout de non-nullables au langage, car cela ressemble à une corbeille syntaxique/sémantique inutile qui est une solution naïve et maladroite à un problème bien connu. Veuillez lire les options en F# et Maybe en Haskell, ainsi que les variantes (c'est-à-dire les unions étiquetées, les unions discriminées) et la correspondance de modèles.

@aleksey-bykov Il semble que vous ne sachiez pas que JavaScript a deux valeurs nulles, undefined et null . La null en JavaScript n'est renvoyée que sur une expression rationnelle non correspondante et lors de la sérialisation d'une date en JSON. La seule raison pour laquelle il est dans le langage était l'interaction avec les applets Java. Les variables qui ont été déclarées mais non initialisées sont undefined , et non null . Les propriétés manquantes sur un objet renvoient undefined , pas null . Si vous voulez explicitement que undefined soit une valeur valide, alors vous pouvez tester propName in obj . Si vous souhaitez vérifier si une propriété existe sur l'objet lui-même plutôt que si elle est héritée, utilisez obj.hasOwnProperty(propName) . Les sous-chaînes manquantes renvoient -1 : 'abc'.indexOf('d') === -1 .

En Haskell, Maybe est utile précisément parce qu'il n'y a pas de sous-type universel. Le type inférieur de Haskell représente la non-terminaison, pas un sous-type universel. Je suis d'accord que les types de données algébriques sont nécessaires, mais si je veux un arbre étiqueté par des entiers, je veux que chaque nœud ait un entier, pas null ou undefined . Si je les veux, j'utiliserai un arbre étiqueté par Maybe int ou une fermeture à glissière.

Si nous adoptons une directive "use not-null", j'aimerais également "use not-void" (ni null ni undefined).

Si vous voulez garantir votre propre code à partir des nulls, interdisez simplement le null
littéraux. C'est beaucoup plus facile que de développer des types non nullables. Indéfini est un
un peu plus compliqué, mais si vous savez d'où ils viennent alors
vous savez comment les éviter. Bottom in Haskell est inestimable ! je souhaite
JavaScript (TypeScript) avait un super type global sans valeur. ça me manque
mal quand j'ai besoin de lancer une expression. J'utilise TypeScript
depuis la v 0.8 et je n'ai jamais utilisé de valeurs nulles et encore moins j'en avais besoin. Ignore juste
comme vous le faites avec n'importe quelle autre fonctionnalité de langage inutile comme with
déclaration.

@aleksey-bykov Si j'écris une bibliothèque et que je veux garantir que les entrées ne sont pas nulles, je dois faire des tests d'exécution partout. Je veux des tests de compilation pour cela, ce n'est pas difficile à fournir, et Closure et Flow prennent en charge les types non null/undefined.

@metaweta , vous ne pouvez pas vous garantir des nulls. Avant que votre code ne soit compilé, il existe des millions de façons de faire pleurer votre bibliothèque : pleaseNonNullablesNumbersOnly(<any> null) . Une fois compilé en js, il n'y a plus de règles du tout. Deuxièmement, pourquoi voudriez-vous vous en soucier ? Dites-le haut et fort dès le départ nulls are not supported, you put a null you will get a crash , comme une clause de non-responsabilité, vous ne pouvez pas vous garantir de toutes sortes de personnes, mais vous pouvez définir l'étendue de vos responsabilités. Troisièmement, je peux difficilement penser à une bibliothèque grand public majeure qui soit à l'épreuve des balles de tout utilisateur qui peut le mettre en entrée, mais elle est toujours follement populaire. Alors, vos efforts valent-ils des ennuis ?

@aleksey-bykov Si les clients de ma bibliothèque sont également vérifiés par type, je peux certainement garantir que je n'obtiendrai pas de null. C'est tout l'intérêt de TypeScript. D'après votre raisonnement, il n'y a aucun besoin de types : il suffit de "dire haut et fort" dans votre documentation quel est le type attendu.

Hors sujet, les valeurs nulles sont extrêmement précieuses pour nous car les vérifier est plus rapide que de les vérifier par rapport à undefined.
Bien que nous ne les utilisons pas partout, nous essayons de les utiliser lorsque cela est possible pour représenter des valeurs non initialisées et des nombres manquants.

Sur le sujet :
Nous n'avons jamais eu de problème avec des valeurs nulles " s'échappant " dans un autre code, mais nous avons eu des problèmes avec des indéfinis aléatoires ou des NaN apparaissant. Je pense qu'une gestion prudente du code vaut mieux qu'un indicateur dans ce scénario.

Cependant, pour les typages de bibliothèque, il serait bien d'avoir le type redondant null afin que nous puissions choisir d'annoter les fonctions qui peuvent retourner null (cela ne devrait pas être imposé par le compilateur, mais par les pratiques de codage).

@metaweta , selon mon raisonnement, vos clients ne devraient pas utiliser de valeurs nulles dans leur base de code, ce n'est pas si difficile, effectuez une recherche complète de null (sensible à la casse, mot entier) et supprimez-les tous. Trop maladroit ? Ajoutez un commutateur de compilateur --noNullLiteral pour plus de fantaisie. Tout le reste reste intact, même code, pas de soucis, solution beaucoup plus légère avec un encombrement minimal. Pour en revenir à mon point, supposons que vos types non nullables aient trouvé leur chemin vers TS et soient disponibles en 2 saveurs différentes :

  • on peut utiliser la syntaxe ! pour désigner un type qui ne peut pas prendre de null, par exemple string! ne peut pas prendre de null
  • Le commutateur noNullsAllowed est activé

alors vous obtenez un morceau de json de votre serveur sur ajax avec des valeurs nulles partout, moral : la nature dynamique de javascript ne peut pas être corrigée par une annotation de type par dessus

@aleksey-bykov De la même manière, si j'attends un objet avec une propriété numérique x et que je reçois {"x":"foo"} du serveur, le système de types ne pourra pas l'empêcher . C'est nécessairement une erreur d'exécution et un problème inévitable lors de l'utilisation de quelque chose d'autre que TypeScript sur le serveur.

Si, toutefois, le serveur est écrit en TypeScript et s'exécute sur un nœud, il peut être transpilé en présence d'un fichier .d.ts pour mon code frontal et la vérification de type garantira que le serveur n'enverra jamais de JSON avec des valeurs NULL ou un objet dont la propriété x n'est pas un nombre.

@metaweta , les types non remets pas en question cela, je dis qu'en imposant une discipline très basique (en évitant les littéraux nuls dans votre code), vous pouvez éliminer 90% de vos problèmes sans demander toute aide du compilateur. Eh bien, même si vous disposez de suffisamment de ressources pour appliquer cette mesure, vous ne pourrez toujours pas éliminer les 10 % restants des problèmes. Alors quelle est la question après tout ? Je demande : en avons-nous vraiment besoin à ce point ? Je ne le fais pas, j'ai appris à vivre sans null avec succès (demandez-moi comment), je ne me souviens pas quand j'ai eu une exception de référence null la dernière fois (en plus des données de notre serveur). Il y a des trucs bien plus cool que j'aurais aimé avoir. Celui-ci en particulier est si insignifiant.

Oui, nous en avons grandement besoin. Voir L'erreur d'un Optional dans une tentative désespérée de le combattre.

La modélisation des nullables dans le système de types n'est pas seulement une préoccupation théorique, c'est une énorme amélioration. Même si vous prenez grand soin d'éviter les valeurs NULL dans votre code, les bibliothèques que vous utilisez peuvent ne pas l'être et il est donc utile de pouvoir modéliser correctement leurs données avec le système de types.

Maintenant qu'il existe des unions et des gardes de type dans TS, le système de type est suffisamment puissant pour le faire. La question est de savoir si cela peut être fait de manière rétrocompatible. Personnellement, je pense que cette fonctionnalité est suffisamment importante pour que TypeScript 2.0 soit rétrocompatible à cet égard.

Implémenter correctement cette fonctionnalité est susceptible de pointer vers du code qui est déjà cassé plutôt que de casser du code existant : il pointera simplement vers les fonctions qui fuient des valeurs NULL en dehors d'elles (probablement par inadvertance) ou des classes qui n'initialisent pas correctement leurs membres (cette partie est plus difficile car le système de types peut avoir besoin de tenir compte des valeurs des membres à initialiser dans les constructeurs).

Il ne s'agit pas de _ne pas utiliser_ de valeurs nulles. Il s'agit de modéliser correctement tous les types impliqués. En fait, cette fonctionnalité permettrait d'utiliser les valeurs NULL de manière sûre - il n'y aurait plus aucune raison de les éviter ! Le résultat final serait très similaire à la correspondance de motifs sur un type algébrique Maybe (sauf que cela serait fait avec une vérification if plutôt qu'une expression case)

Et il ne s'agit pas que de littéraux nuls. null et undefined sont structurellement les mêmes (si je ne m'abuse, il n'y a pas de fonctions/opérateurs qui fonctionnent sur l'un mais pas sur l'autre) donc ils pourraient être suffisamment bien modélisés avec un seul type nul dans TS.

@metaweta ,

La valeur nulle en JavaScript n'est renvoyée que sur une expression rationnelle non correspondante et lors de la sérialisation d'une date en JSON.

Pas vrai du tout.

  • L'interaction avec le DOM produit null :
  console.log(window.document.getElementById('nonExistentElement')); // null
  • Comme @aleksey-bykov l'a souligné ci-dessus, les opérations ajax peuvent renvoyer null. En fait, undefined n'est pas une valeur JSON valide :
 JSON.parse(undefined); // error
 JSON.parse(null); // okay
 JSON.stringify({ "foo" : undefined}); // "{}"
 JSON.stringify({ "foo" : null}); // '{"foo":null}'

NB : Nous pouvons prétendre que undefined est renvoyé via ajax, car accéder à une propriété inexistante entraînera undefined - c'est pourquoi undefined n'est pas sérialisé.

Si, toutefois, le serveur est écrit en TypeScript et s'exécute sur un nœud, il peut être transpilé en présence d'un fichier .d.ts pour mon code frontal et la vérification de type garantira que le serveur n'enverra jamais de JSON avec des valeurs NULL dedans ou un objet dont la propriété x n'est pas un nombre.

Ce n'est pas tout à fait correct. Même si le serveur est écrit en TypeScipt, on ne peut en aucun cas garantir l'introduction de valeurs NULL sans vérifier chaque propriété de chaque objet obtenu à partir du stockage persistant.

Je suis plutôt d'accord avec @aleksey-bykov à ce sujet. Bien que ce serait absolument génial si nous pouvions demander à TypeScript de nous alerter au moment de la compilation des erreurs introduites par null et undefined, je crains que cela n'induise un faux sentiment de confiance et finisse par attraper des anecdotes alors que les vraies sources de null ne sont pas détectées.

Même si le serveur est écrit en TypeScipt, on ne peut en aucun cas garantir l'introduction de valeurs NULL sans vérifier chaque propriété de chaque objet obtenu à partir du stockage persistant.

Il s'agit en fait d'un argument _pour_ les types non nullables. Si votre stockage peut renvoyer des Foo nuls, le type de l'objet récupéré à partir de ce stockage est Nullable<Foo>, pas Foo. Si vous avez alors une fonction qui renvoie qui est censée renvoyer Foo, alors vous _devez_ assumer la responsabilité en manipulant le null (soit vous le lancez parce que vous en savez mieux, soit vous vérifiez le null).

Si vous n'aviez pas de types non nullables, vous ne penseriez pas nécessairement à vérifier null lors du renvoi de l'objet stocké.

Je crains que cela ne fasse qu'induire un faux sentiment de confiance et finisse par attraper des anecdotes alors que les vraies sources de nullité ne sont pas détectées.

À votre avis, quel genre de non-trivia manquera aux types non nullables ?

Ce n'est pas tout à fait correct. Même si le serveur est écrit en TypeScipt, on ne peut en aucun cas garantir l'introduction de valeurs NULL sans vérifier chaque propriété de chaque objet obtenu à partir du stockage persistant.

Si le stockage persistant prend en charge les données saisies, cela n'est pas nécessaire. Mais même si ce n'était pas le cas, vous auriez des vérifications uniquement aux points de récupération des données, puis vous auriez une garantie sur _tout_ votre autre code.

Je suis plutôt d'accord avec @aleksey-bykov à ce sujet. Bien que ce serait absolument génial si nous pouvions demander à TypeScript de nous alerter au moment de la compilation des erreurs introduites par null et undefined, je crains que cela n'induise un faux sentiment de confiance et finisse par attraper des anecdotes alors que les vraies sources de null ne sont pas détectées.

L'utilisation de types nullables ne serait pas une exigence absolue. Si vous pensez qu'il n'est pas nécessaire de modéliser les cas où une méthode renvoie null car ils sont "insignifiants", vous ne pouvez tout simplement pas utiliser un type nullable dans cette définition de type (et obtenir la même insécurité que toujours). Mais il n'y a aucune raison de penser que cette approche échouera - il existe des exemples de langages qui l'ont déjà implémentée avec succès (par exemple Kotlin de JetBrains )

@aleksey-bykov Honnêtement, vous vous trompez complètement, l'une des meilleures choses à propos des types non nullables est _la possibilité d'exprimer un type comme nullable_.
Avec votre stratégie de ne jamais utiliser null pour éviter une erreur de pointeur null, vous perdez complètement la possibilité d'utiliser null par peur d'introduire une erreur, c'est complètement idiot.

Une autre chose s'il vous plaît dans une discussion sur une fonctionnalité de langue, n'allez pas avec des commentaires stupides comme :

Les types non nullables sont inutiles. Les types non nullables sont inutiles. Ils sont inutiles. Inutile! Vous ne vous en rendez pas compte, mais vous n'en avez pas vraiment besoin.

Cela me donne juste l'impression que je devrais ignorer tout ce que vous publierez n'importe où sur le Web, nous sommes ici pour discuter d'une fonctionnalité, je peux comprendre et accepter volontiers que votre point de vue n'est pas le mien, mais ne vous comportez pas comme un enfant.

Je ne suis pas du tout contre l'introduction d'annotations de type non nul. Il s'est avéré utile en C# et dans d'autres langages.

Le PO a changé le cours de la discussion avec ce qui suit :

Honnêtement, si l'ajout de use not-null est le prix à payer pour éviter toutes les exceptions du pointeur null, je le paierais sans aucun problème, considérer null ou undefined comme assignable à n'importe quel type est la pire erreur que le tapuscrit a faite à mon avis.

Je soulignais simplement la prévalence de null et de undefined dans la nature.

Je dois également ajouter que l'une des choses que j'apprécie vraiment à propos de TypeScript est l'attitude de laisser-faire du langage. Cela a été une bouffée d'air frais.

Insister sur le fait que les types ne sont pas nullables par défaut va à l'encontre de cet esprit.

J'ai vu un certain nombre d'arguments circuler pour expliquer pourquoi nous en avons besoin / n'en avons pas besoin et je veux voir si je comprends tous les cas sous-jacents qui ont été discutés jusqu'à présent :

1) voulez savoir si une fonction peut ou non retourner null (causé par son modèle d'exécution, pas par sa frappe).
2) veulent savoir si une valeur peut être nulle.
3) veulent savoir si un objet de données peut contenir des valeurs nulles.

Maintenant, il n'y a que deux cas dans lesquels la situation 2 peut se produire : si vous utilisez des valeurs NULL ou si une fonction renvoie une valeur NULL. Si vous éliminez toutes les valeurs NULL de votre code (en supposant que vous ne les voulez pas), alors la situation 2 ne peut vraiment se produire qu'à la suite de la situation 1.

Je pense que la situation 1 est mieux résolue en annotant le type de retour de la fonction pour montrer la présence d'une valeur nulle. _Cela ne signifie pas que vous avez besoin d'un type non nul_. Vous pouvez annoter la fonction (par exemple en utilisant des types union) et ne pas avoir de types non nuls, c'est comme la documentation, mais probablement plus clair dans ce cas.

La solution 2 est également résolue par ceci.

Cela permet aux programmeurs travaillant dans leur entreprise d'utiliser des processus et des normes pour imposer que les types nuls soient balisés, et non l'équipe Typescript (exactement de la même manière que l'ensemble du système de typage est un opt-in, de sorte que les types explicites nullables seraient un opt dans).

En ce qui concerne le scénario 3, le contrat entre le serveur et le client n'est pas à appliquer par Typescript, être en mesure de marquer les valeurs affectées comme éventuellement nulles pourrait être une amélioration, mais vous obtiendrez finalement la même garantie que celle de Typescript. l'outillage vous donne toutes les autres valeurs (c'est-à-dire aucune, à moins que vous n'ayez de bonnes normes ou pratiques de codage !

(publication depuis le téléphone, désolé pour les erreurs)

@fdecampredon , ce n'est pas la peur en premier lieu, c'est que l'utilisation de null est inutile. Je n'ai pas besoin d'eux. En prime, j'ai éliminé un problème d'exceptions de référence nulle. Comment est-ce possible ? En employant un type somme avec un cas vide. Les types de somme sont une fonctionnalité native de tous les langages FP comme F#, Scala, Haskell et avec les types de produits appelés types de données algébriques. Des exemples standard de types de somme avec une casse vide seraient Facultatif de F# et Maybe de Haskell. TypeScript n'a pas d'ADT, mais à la place, il a une discussion en cours sur l'ajout de non-nullables qui modéliseraient un cas particulier de ce que les ADT auraient couvert. Mon message est donc d'abandonner les non-nullables pour les ADT.

@spion , Mauvaise nouvelle : F# a obtenu des

Nous utilisons null mais pas de la même manière que vous. Le code source de mon entreprise se compose de 2 parties.

1) En ce qui concerne les données (comme les données de la base de données), nous remplaçons null par des données vides au moment de la déclaration de la variable.

2) Tous les autres, comme les objets programmables, nous utilisons null afin que nous sachions quand il y a un bogue et une faille dans le code source où les objets ne sont pas créés ou assignables qui ne sont pas des objets javascript nécessaires. L'indéfini est un problème d'objet javascript où il y a un bogue ou une faille dans le code source.

Les données que nous ne voulons pas être nulles car il s'agit de données client et ils verront des libellés nuls.

@aleksey-bykov typescript a adt avec type union, la seule chose qui manque est la correspondance de modèle mais c'est une fonctionnalité qui ne peut tout simplement pas être implémentée avec la philosophie dactylographiée de générer du javascript proche de la source d'origine.

D'un autre côté, il est impossible de définir avec le type union l'exclusion de la valeur null, c'est pourquoi nous avons besoin d'un type non null.

@fdecampredon , TS n'a pas d'ADT, il a des unions qui ne sont pas des types de somme, car, comme vous l'avez dit, 1. ils ne peuvent pas modéliser correctement un cas vide car il n'y a pas de type d'unité, 2. il n'y a pas de moyen fiable de déstructurer eux

la correspondance de modèle pour les ADT peut être implémentée d'une manière étroitement alignée avec le JavaScript généré, de toute façon j'espère que cet argument n'est pas un tournant

@aleksey-bykov ce n'est pas F#. C'est un langage qui vise à modéliser les types de JavaScript. Les bibliothèques JavaScript utilisent des valeurs nulles et non définies. Par conséquent, ces valeurs doivent être modélisées avec les types appropriés. Étant donné que même ES6 ne prend pas en charge les types de données algébriques, cela n'a pas de sens d'utiliser cette solution compte tenu des objectifs de conception de TypeScript

De plus, les programmeurs JavaScript utilisent généralement des vérifications if (en conjonction avec des tests de typeof et d'égalité), au lieu de la correspondance de modèle. Ceux-ci peuvent déjà restreindre les types d'union TypeScript. À partir de ce point, ce n'est qu'un petit pas pour prendre en charge les types non nullables avec des avantages comparables à ceux de l'algébrique Maybe, etc.

Je suis surpris que personne n'ait réellement mentionné les énormes changements que lib.d.ts peut avoir besoin d'introduire et les problèmes potentiels de l'état nul transitoire des champs de classe lors de l'invocation du constructeur. Ce sont des problèmes potentiels réels et réels pour implémenter des types non nullables ...

@Griffork l'idée est d'éviter d'avoir des contrôles nuls partout dans votre code. Disons que vous avez la fonction suivante

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

L'idée est que vous vérifiez si le nom est null une seule

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

Et maintenant tu es super ! Le système de types garantit que doThingsWith sera appelé avec un nom qui n'est pas nul

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

Aucune de ces fonctions n'a besoin de rechercher un null, et le code fonctionnera toujours sans lancer. Et, dès que vous essayez de passer une chaîne nullable à l'une de ces fonctions, le système de types vous dira immédiatement que vous avez fait une erreur :

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

C'est un énorme avantage : maintenant, le système de types suit si les fonctions peuvent gérer des valeurs nullables ou non, et en outre, il suit si elles en ont réellement besoin ou non. Code beaucoup plus propre, moins de contrôles, plus de sécurité. Et il ne s'agit pas seulement de chaînes - avec une bibliothèque comme runtime-type-checks, vous pouvez également créer des protections de type pour des données beaucoup plus complexes

Et si vous n'aimez pas le suivi parce que vous pensez que cela ne vaut pas la peine de modéliser la possibilité d'une valeur nulle, vous pouvez revenir au bon vieux comportement dangereux :

declare function getName(personId:number):string;

et dans ces cas, tapuscrit ne vous avertira que si vous faites quelque chose qui est manifestement faux

uppercasedName(null);

Franchement, je ne vois pas d'inconvénients, à l'exception de la compatibilité descendante.

@fdecampredon Les types d'union ne sont que ça, des syndicats. Ce ne sont pas des unions disjointes, c'est-à-dire des sommes. Voir #186.

@aleksey-bykov

Notez que l'ajout d'un type d'option sera toujours un changement décisif.

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

Après tout, vous pouvez obtenir les types d'options d'un pauvre dès maintenant avec la déstructuration

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

mais la seule valeur de ceci est si lib.d.ts (et tout autre .d.ts, et toute votre base de code, mais nous supposerons que nous pouvons résoudre ce problème) l'utilise également, sinon vous ne savez plus si un fonction qui n'utilise pas Option peut retourner null ou non à moins que vous ne regardiez son code.

Notez que je ne suis pas non plus favorable à ce que les types soient non nuls par défaut (pas pour TS 1.x en tout cas). C'est un changement trop important.

Mais disons que nous parlons de 2.0. Si nous allons de toute façon avoir un changement radical (ajout de types d'options), pourquoi ne pas également rendre les types non nullables par défaut ? Rendre les types non nullables par défaut et ajouter des types d'options n'est pas exclusif. Ce dernier peut être autonome (par exemple en F# comme vous le soulignez) mais le premier nécessite le second.

@Arnavion , il y a un malentendu, je n'ai pas dit que nous devions remplacer les signatures, toutes les signatures existantes restent intactes, c'est pour les nouveaux développements que vous êtes libre d'aller soit avec ADT ou tout ce que vous voulez. Donc pas de changements de rupture. Rien n'est rendu non nul par défaut.

Si les ATD sont présents, il appartient au développeur d'envelopper tous les emplacements où des valeurs NULL peuvent s'infiltrer dans l'application en les transformant ensuite en options. Cela peut être une idée pour un projet autonome.

@aleksey-bykov

Je n'ai pas dit que nous devions remplacer les signatures, toutes les signatures existantes restent intactes

_J'ai dit que les signatures devaient être remplacées, et j'ai déjà donné la raison :

mais la seule valeur de ceci est si lib.d.ts (et tout autre .d.ts, et toute votre base de code, mais nous supposerons que nous pouvons résoudre ce problème) l'utilise également, sinon vous ne savez plus si un fonction qui n'utilise pas Option peut retourner null ou non à moins que vous ne regardiez son code.


Rien n'est rendu non nul par défaut. Jamais.

Pour TS 1.x, je suis d'accord, uniquement parce que c'est un changement trop important. Pour 2.0, l'utilisation du type d'option dans les signatures par défaut (lib.d.ts, etc.) serait déjà un changement décisif. Rendre les types non nullables par défaut _en plus de cela_ en vaut la peine et n'entraîne aucun inconvénient.

Je ne suis pas d'accord, l'introduction d'options ne devrait rien casser, ce n'est pas comme si nous utilisions des options, des nullables ou des non nullables. Chacun utilise ce qu'il veut. L'ancienne façon de faire les choses ne devrait pas dépendre de nouvelles fonctionnalités. C'est au développeur d'utiliser un outil adapté à ses besoins immédiats.

Donc, vous dites que si j'ai une fonction foo qui renvoie Option<nombre> et une barre de fonction qui renvoie un nombre, je ne suis pas autorisé à être sûr que bar ne peut pas renvoyer null à moins que je regarde l'implémentation de bar ou que je maintienne la documentation "Cette fonction ne renvoie jamais null."? Ne pensez-vous pas que cela punit les fonctions qui ne retourneront jamais null?

la fonction bar de votre exemple était connue sous le nom de nullable depuis le début des temps, elle a été utilisée dans quelque 100500 applications tout autour et tout le monde a traité le résultat comme nullable, maintenant vous êtes venu et avez regardé à l'intérieur et découvert que null est impossible, cela signifie-t-il que vous devez aller de l'avant et changer la signature de nullable à non nullable ? Je pense que tu ne devrais pas. Car ces connaissances, bien que précieuses, ne valent pas la peine de freiner 100500 applications. Ce que vous devez faire, c'est créer une nouvelle bibliothèque avec des signatures révisées qui fonctionnent comme ceci :

old_lib.d.ts

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

révisé_lib.d.ts

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

maintenant, les applications héritées continuent d'utiliser le old_lib.d.ts , pour les nouvelles applications, un développeur est libre de choisir revised_libs.d.ts

Malheureusement révisé_libs.d.ts a 50 autres fonctions que je n'ai pas encore examinées, qui renvoient toutes un nombre (mais je ne sais pas s'il s'agit vraiment d'un nombre ou d'un nombre nullable). Et maintenant?

Eh bien, prenez votre temps, demandez de l'aide, utilisez la gestion des versions (selon le niveau de connaissances que vous avez acquis jusqu'à présent, vous voudrez peut-être le publier progressivement avec un numéro de version toujours croissant : revised_lib.v-0.1.12.d.ts )

Ce n'est pas nécessaire en fait. Une fonction qui renvoie un type nullable dans la signature mais un type non nullable dans l'implémentation entraîne uniquement une vérification d'erreur redondante par l'appelant. Cela ne compromet pas la sécurité. Annoter progressivement de plus en plus de fonctions avec ! au fur et à mesure que vous les découvrirez, cela fonctionnera très bien, comme vous l'avez dit.

Je ne suis pas fan de ! uniquement parce que c'est plus lourd à taper (à la fois en termes de frappes et de besoin de se rappeler de l'utiliser). Si nous voulons la non-nullabilité dans 1.x alors ! est l'une des options déjà discutées ci-dessus, mais je dirais toujours qu'avoir un éventuel changement de rupture avec 2.0 et faire de la non-nullabilité la valeur par défaut en vaut la peine.

D'un autre côté, cela conduira peut-être à une situation Python 2/3-esque, où personne ne passera à TS 2.0 pendant des années parce qu'ils ne peuvent pas se permettre de parcourir leur base de code d'un million de lignes en s'assurant que chaque déclaration de variable et classe paramètre de membre et de fonction et... est annoté avec ? s'il peut être nul. Même 2to3 (l'outil de migration Python 2 à 3) n'a pas à faire face à des changements de grande envergure comme celui-là.

Que la 2.0 puisse se permettre d'être un changement décisif dépend de l'équipe TS. Je voterais pour oui, mais je n'ai pas une base de code d'un million de lignes qui devra être corrigée, alors peut-être que mon vote ne compte pas.

Peut-être devrions-nous demander aux gens de Funscript comment ils réconcilient l'API DOM renvoyant des valeurs NULL avec F# (Funscript utilise lib.d.ts de TypeScript et d'autres .d.ts pour une utilisation à partir du code F#). Je ne l'ai jamais utilisé, mais en regardant http://funscript.info/samples/canvas/index.html par exemple, il semble que le fournisseur de type ne pense pas que document.getElementsByTagName("canvas")[0] puisse jamais être indéfini.

Edit: Ici, il semble que document.getElementById() ne devrait pas renvoyer null. À tout le moins, il ne semble pas renvoyer Option<Element> car il accède à .onlick sur le résultat.

@spion
Merci, je n'y avais pas pensé.

Au travail, notre base de code n'est pas petite, et ce changement radical que les gens veulent nous ferait gagner beaucoup de temps avec très peu de gain. Grâce à de bonnes normes et à une communication claire entre nos développeurs, nous n'avons pas eu de problèmes avec des nulls apparaissant là où ils ne devraient pas.
Je suis honnêtement surpris que certaines personnes poussent si mal pour cela.
Inutile de dire que cela nous apporterait très peu d'avantages et nous coûterait beaucoup de temps.

@Griffork a examiné cette liste filtrée de problèmes dans le compilateur dactylographié pour une estimation de l'importance des avantages que ce changement pourrait apporter. Tous les bogues de "crash" répertoriés sur ce lien pourraient être évités en utilisant des types non nullables. Et nous parlons ici du niveau impressionnant des normes, de la communication et de la révision du code de Microsoft.

En ce qui concerne la casse, je pense que si vous continuez à utiliser les définitions de type existantes, il est possible que vous n'obteniez aucune erreur, à l'exception du compilateur signalant des variables et des champs potentiellement non initialisés. D'un autre côté, vous pourriez obtenir beaucoup d'erreurs, en particulier si les champs de classe sont souvent laissés non initialisés dans les constructeurs de votre code (à initialiser plus tard). Par conséquent, je comprends vos inquiétudes et je ne préconise pas un changement rétrocompatible pour TS 1.x. J'espère toujours avoir réussi à vous persuader que si un changement de langue méritait de rompre la compatibilité descendante, c'est bien celui-ci.

Dans tous les cas, le flux de Facebook a des types non nullables. Une fois qu'il sera plus mature, cela pourrait valoir la peine d'être étudié en remplacement de TS pour ceux d'entre nous qui se soucient de ce problème.

@spion , le seul nombre que votre liste nous donne est le nombre de fois où null ou undefined ont été mentionnés pour une raison quelconque, en gros, cela dit seulement que null et undefined ont été évoqués, j'espère qu'il est clair que vous ne pouvez pas l'utiliser comme argument

@aleksey-bykov Je ne le suis pas - j'ai examiné _tous_ les problèmes de cette liste filtrée et tous ceux qui contenaient le mot "crash" étaient liés à une trace de pile qui montre qu'une fonction a tenté d'accéder à une propriété ou à une méthode d'une valeur indéfinie ou nulle.

J'ai essayé d'affiner le filtre avec différents mots-clés et j'ai (pense que j'ai) réussi à tous les obtenir

@spion
Question : combien de ces erreurs sont causées à des emplacements dont ils auraient besoin pour marquer la variable comme nullable ou indéfinissable ?

Par exemple, si un objet peut avoir un parent et que vous initialisez le parent à null, et que vous aurez toujours un objet avec un parent null, vous devrez toujours déclarer le parent comme éventuellement null.
Le problème ici est qu'un programmeur écrit du code en supposant qu'une boucle se brisera toujours avant d'atteindre le parent nul. Ce n'est pas un problème avec null étant dans la langue, exactement la même chose se produirait avec undefined .

Raisons pour lesquelles null est aussi facile à utiliser qu'aujourd'hui :
• C'est une meilleure valeur par défaut qu'undefined.
. 1) c'est plus rapide à vérifier dans certains cas (notre code doit être très performant)
. 2) cela permet aux boucles de fonctionner de manière plus prévisible sur les objets.
. 3) cela rend l'utilisation du tableau plus logique lors de l'utilisation de valeurs nulles pour les valeurs vides (par opposition aux valeurs manquantes). Notez que delete array[i] et array[i] = undefined ont un comportement différent lors de l'utilisation d'indexOf (et probablement d'autres méthodes de tableau populaires).

Ce que je pense que le résultat de la création de valeurs nulles nécessite un balisage supplémentaire à utiliser dans le langage :
• J'ai eu une erreur indéfinie au lieu d'une erreur nulle (ce qui se produirait dans la plupart des scénarios tapuscrits).

Quand j'ai dit que nous n'avions pas de problème avec l'échappement des valeurs NULL, je voulais dire que les variables qui ne sont pas initialisées à NULL ne deviennent jamais NULL, nous obtenons toujours des erreurs d'exception NULL exactement au même endroit où nous obtiendrions des erreurs indéfinies (comme le fait l'équipe Typescript ). Rendre null plus difficile à utiliser (en exigeant une syntaxe supplémentaire) et le laisser indéfini posera en fait plus de problèmes à certains développeurs (par exemple, nous).

L'ajout d'une syntaxe supplémentaire pour utiliser null signifie que pendant plusieurs semaines/mois, les développeurs qui utilisent beaucoup de null feront des erreurs à gauche, à droite et au centre pendant qu'ils essaieront de se souvenir de la nouvelle syntaxe. Et ce sera une autre façon de déraper à l'avenir (en annotant quelque chose de manière légèrement incorrecte). [Il est temps de souligner que je déteste l'idée d'utiliser des symboles pour représenter des types, cela rend le langage moins clair]

Jusqu'à ce que vous puissiez m'expliquer une situation dans laquelle null provoque une erreur ou un problème que undefined ne serait pas, alors je ne serai pas d'accord pour rendre null beaucoup plus difficile à utiliser que undefined. Il a son cas d'utilisation, et ce n'est pas parce que cela ne vous aide pas que le changement radical que vous souhaitez (qui _nuira_ au flux de travail des autres développeurs) devrait avoir lieu.

Conclusion:
Il ne sert à rien de pouvoir déclarer des types non nullables sans pouvoir définir des types non indéfinis. Et les types non définis ne sont pas possibles en raison de la façon dont javascript fonctionne.

@Griffork quand je dis non nullable, je veux dire non nullable ou non défini. Et ce n'est pas vrai que ce n'est pas possible en raison de la façon dont JS fonctionne. Avec les nouvelles fonctionnalités de type guard, une fois que vous utilisez un type guard, vous savez que la valeur qui en découle ne peut plus être nulle ou indéfinie. Il y a là aussi un compromis à faire, et je soumets la mise en

Le cas _seulement_ qui deviendra légèrement plus difficile à utiliser est celui-ci : j'affecterai temporairement null à cette variable puis je l'utiliserai dans cette autre méthode comme si ce n'était pas null, mais je sais que je n'appellerai jamais cette autre méthode avant initialiser la variable en premier, il n'est donc pas nécessaire de vérifier les valeurs NULL. C'est un code très fragile, et j'apprécierais certainement que le compilateur m'en avertisse : c'est une refactorisation loin d'être un bogue de toute façon.

@spion
Je crois que je comprends enfin d'où vous venez.

Je pense que vous voulez que les gardes de type aident à déterminer quand une valeur ne peut pas être nulle et vous permettent d'appeler des fonctions qui ne vérifient pas la valeur nulle à l'intérieur. Et si l'instruction if de garde est supprimée, cela devient une erreur.

Je peux voir que c'est utile.

Je crois aussi que ce ne sera pas vraiment la solution miracle que vous espérez.

Un compilateur qui n'exécute pas votre code et n'en teste pas toutes les facettes ne sera pas meilleur pour déterminer où se trouvent les non définis/nulls que le programmeur qui a écrit le code. Je crains que ce changement n'endorme les gens dans un faux sentiment de sécurité et rende en fait les erreurs nulles/indéfinies plus difficiles à suivre lorsqu'elles se produisent.
Vraiment, je pense que la solution dont vous avez besoin est un bon ensemble d'outils de test qui prennent en charge Typescript, qui peuvent reproduire ce genre de bogues en Javascript, plutôt que d'implémenter un type dans un compilateur qui ne peut pas tenir sa promesse.

Vous mentionnez Flow comme ayant une solution à ce problème, mais en lisant votre lien, j'ai vu des choses inquiétantes :

« Flow peut parfois également ajuster les types de propriétés d'objets, en particulier lorsqu'il n'y a pas d'opérations intermédiaires entre un contrôle et une utilisation. En général, cependant, l' aliasing des objets limite la portée de cette forme de raisonnement , car un contrôle sur une propriété d'objet peut être invalidé par une écriture sur cette propriété via un alias, et il est difficile pour une analyse statique de suivre les alias avec précision ."

"Les valeurs non définies, tout comme null, peuvent également causer des problèmes. Malheureusement, les valeurs non définies sont omniprésentes dans JavaScript et il est difficile de les éviter sans affecter gravement la convivialité du langage [...] Flow fait un compromis dans ce cas : il détecte les variables locales indéfinies et les valeurs de retour, mais ignore la possibilité d'un indéfini résultant des accès aux propriétés de l'objet et aux éléments du tableau ."

Maintenant, undefined et null fonctionnent différemment, des erreurs non définies peuvent toujours apparaître partout, le point d'interrogation ne peut pas garantir que la valeur n'est pas null, et le langage se comporte plus différemment de Javascript (ce que TS essaie d'éviter de ce que j'ai vu ).

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 est déjà vulnérable aux effets d'alias (comme tout langage qui autorise les valeurs mutables). Cela compilera sans aucune erreur :

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

Les documents Flow indiquent cela uniquement par souci d'exhaustivité.

Edit : meilleur exemple.

Vieille:

Oui, mais je soutiens que le moyen de définir quelque chose sur indéfini est beaucoup plus grand que le moyen de définir quelque chose sur une autre valeur.

Je veux dire,

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

Éditer:
Meh, à ce stade, c'est à peu près une préférence personnelle. Après avoir utilisé javascript pendant des siècles, je ne pense pas que cette suggestion va aider la langue. Je pense que cela va bercer les gens dans un faux sentiment de sécurité, et je pense que cela éloignera Typescript (en tant que langage) de Javascript.

@Griffork Pour un exemple de la façon dont le texte dactylographié actuel vous donne un faux sentiment de sécurité, essayez l'exemple que vous avez présenté dans le terrain de jeu :

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

Soit dit en passant, l'opérateur de suppression pourrait être traité de la même manière que l'attribution d'une valeur de type null et cela suffirait également à couvrir votre cas.

L'affirmation selon laquelle le langage se comportera différemment de JavaScript est vraie, mais dénuée de sens. TypeScript a déjà un comportement différent de JavaScript. Le but d'un système de types a toujours été d'interdire les programmes qui n'ont pas de sens. La modélisation de types non nullables ajoute simplement quelques restrictions supplémentaires. Interdire l'affectation de valeurs nulles ou indéfinies à une variable de type non-nullable revient exactement à interdire l'affectation d'un nombre à une variable de type chaîne. JS autorise les deux, TS ne peut autoriser ni l'un ni l'autre

@spion

l'idée est d'éviter d'avoir des contrôles nuls partout dans votre code

Si je comprends ce que vous préconisez :

A. Rendez tous les types non nuls par défaut.
B. Marquez les champs et les variables qui sont nullables.
C. Assurez-vous que le développeur de l'application/de la bibliothèque vérifie tous les points d'entrée dans l'application.

Mais cela ne signifie-t-il pas que la responsabilité de s'assurer que son code est exempt de valeurs NULL incombe à la personne qui écrit le code, et non au compilateur ? Nous disons effectivement au compilateur « dw, je ne laisse aucune valeur NULL dans le système ».

L'alternative est de dire que les valeurs NULL sont partout, alors ne vous embêtez pas, mais si quelque chose n'est pas NULL, je vous le ferai savoir.

Le fait est que cette dernière approche est sujette aux exceptions de référence nulle, mais elle est plus véridique. Prétendre qu'un champ sur un objet obtenu sur le fil (ie ajax) est non nul implique d'avoir foi en Dieu :smiley: .

Je crois qu'il y a un fort désaccord sur cette question car, selon ce sur quoi on travaille, l'élément C ci-dessus pourrait être insignifiant ou infaisable.

@jbondc Je suis content que vous ayez demandé cela. En effet, CallExpression est marqué comme non défini ou nullable. Cependant, le système de types ne tire actuellement aucun avantage de cela - il autorise toujours toutes les opérations sur typeArguments comme s'il n'était pas nul ou indéfini.

Cependant, lorsque vous utilisez les nouveaux types d'union en combinaison avec des types non nullables, le type peut être exprimé sous la forme NodeArray<TypeNode>|null . Ensuite, le système de types n'autorisera aucune opération sur ce champ à moins qu'une vérification nulle ne soit appliquée :

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

// callSomethingOn doesn't need to perform any checks

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

Avec l'aide des gardes de type TS 1.4, à l'intérieur du bloc if, le type de l'expression sera réduit à NodeArray<TypeNode> ce qui permettra à son tour toutes les opérations NodeArray sur ce type ; de plus, toutes les fonctions appelées dans cette vérification pourront spécifier que leur type d'argument sera NodeArray<TypeNode> sans effectuer plus de vérifications, jamais.

Mais si tu essaies d'écrire

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

le compilateur vous en avertira au moment de la compilation, et le bogue ne serait tout simplement pas arrivé.

Alors non, @NoelAbrahams , il ne s'agit pas de tout savoir avec certitude. Il s'agit du compilateur qui vous aide à dire quelle valeur une variable ou un champ peut contenir, comme avec tous les autres types.

Bien sûr, avec les valeurs externes, comme toujours, c'est à vous de spécifier quels sont leurs types. Vous pouvez toujours dire que les données externes contiennent une chaîne au lieu d'un nombre, et le compilateur ne se plaindra pas mais votre programme plantera en essayant d'effectuer des opérations de chaîne sur le nombre.

Mais sans types non nullables, vous n'avez même pas la possibilité de dire qu'une valeur ne peut pas être null. La valeur nulle est attribuable à chaque type et vous ne pouvez faire aucune restriction à son sujet. Les variables peuvent ne pas être initialisées et vous ne recevrez aucun avertissement car undefined est une valeur valide pour n'importe quel type. Par conséquent, le compilateur n'est pas en mesure de vous aider à détecter les erreurs liées aux valeurs nulles et non définies au moment de la compilation.

Je trouve surprenant qu'il y ait autant d'idées fausses sur les types non nullables. Ce sont juste des types qui ne peuvent pas être non initialisés ou qui ne peuvent pas recevoir les valeurs null ou undefined . Ce n'est pas si différent de l'impossibilité d'attribuer une chaîne à un nombre. Ceci est mon dernier post sur cette question, car j'ai l'impression que je n'avance pas vraiment. Si quelqu'un souhaite en savoir plus, je recommande de commencer par la vidéo "L'erreur d'un milliard de dollars" que j'ai mentionnée ci-dessus. Le problème est bien connu et résolu avec succès par de nombreux langages modernes et compilateurs.

@spion , je suis entièrement d'accord sur tous les avantages de pouvoir indiquer si un type peut ou non être nul. Mais la question est : voulez-vous que les types soient non nuls _par défaut _ ?

Prétendre qu'un champ sur un objet obtenu sur le fil (c'est-à-dire ajax) est non nul implique d'avoir foi en Dieu

Alors ne fais pas semblant. Marquez-le comme nullable et vous serez obligé de le tester avant de l'utiliser comme non nullable.

Sûr. Cela revient à savoir si nous voulons marquer un champ comme nullable (dans un système où les champs sont non nullables par défaut) ou si nous voulons marquer un champ comme non nullable (dans un système où les champs sont nullables par défaut).

L'argument est que le premier est un changement décisif (qui peut ou non être important) et également intenable car il oblige le développeur de l'application à vérifier et à garantir tous les points d'entrée.

@NoelAbrahams Je ne vois pas pourquoi c'est "intenable" fondamentalement la plupart du temps, vous ne voulez pas null, également lorsqu'un point d'entrée peut retourner null, vous devrez le vérifier , à la fin un système de type avec un type non nul par défaut, cela vous permettra de faire moins de vérifications nulles car vous pourrez faire confiance à un point d'api/library/entry dans votre application.

Lorsque vous y réfléchissez un peu, marquer un type comme non nul dans un système de type nullable a une valeur limitée, vous pourrez toujours consommer une variable typée/un type de retour nullable sans être obligé de le tester.
Cela forcera également l'auteur de la définition à écrire plus de code, car la plupart du temps, une bibliothèque bien conçue ne renvoie jamais de valeur nulle ou indéfinie.
Enfin même le concept est étrange, dans un système de types avec un type non nullable, un type nullable est parfaitement exprimable comme un type union : ?string est l'équivalent de string | null | undefined . Dans un système de types avec un type nullable par défaut où vous pouvez marquer le type comme non nullable, comment exprimeriez-vous !string ? string - null - undefined ?

Au final je ne comprends pas trop l'inquiétude des gens ici, null n'est pas une chaîne, de la même manière que 5 n'est pas une chaîne, les deux valeurs ne pourront pas être utilisé là où une chaîne est attendue, et laisser glisser var myString: string = null est aussi sujet aux erreurs que : var myString: string = 5 .
Avoir une assignation nulle ou indéfinie à n'importe quel type est peut-être un concept avec lequel les développeurs sont familiers, mais c'est toujours un mauvais concept.

Je ne pense pas avoir tout à fait raison dans mon post précédent : je vais blâmer l'heure.

Je viens de parcourir une partie de notre code pour voir comment les choses fonctionneraient et cela aiderait certainement à marquer certains champs comme nullables, par exemple :

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

Mais ce à quoi je m'oppose est le suivant :

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

Je pense que cela interférera avec la manière naturelle de travailler avec JavaScript.

La même chose pourrait s'appliquer avec le nombre :

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

foo.name  = 5 // error

Et c'est toujours valable en JavaScript

Raisonnablement combien de fois attribuez-vous volontairement null à une propriété d'un objet ?

Je pense que la plupart des choses seraient marquées comme nulles, mais vous compteriez sur des gardes de type pour déclarer que le champ est désormais non nullable.

@fdecampredon
Beaucoup en fait.

@Griffork ,

Je pense que la plupart des choses seraient marquées comme nulles

C'était ma pensée initiale. Mais après avoir parcouru certaines sections de notre code, j'ai trouvé des commentaires tels que les suivants :

interface MyType {

     name: string;

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

L'idée qu'un champ est nullable est souvent utilisée pour fournir des informations. Et TypeScript détectera les erreurs s'il nécessite une protection de type lors de l'accès à wikiDate .

@fdecampredon

foo.name = 5 // error
Et c'est toujours valable en JavaScript

C'est vrai, mais c'est une erreur car TypeScript sait avec une certitude à 100% que ce n'était pas intentionnel.

tandis que

foo.name = non défini ; // Ne pas envoyer le nom au serveur

est parfaitement intentionnel.

Je pense que l'implémentation qui correspondrait le mieux à nos exigences consiste à ne pas utiliser de types d'union, mais à suivre la suggestion d'origine :

 wikiDate: ?Date;

Je suis d'accord avec @NoelAbrahams

foo.name = 5 // erreur
Et c'est toujours valable en JavaScript
C'est vrai, mais c'est une erreur car TypeScript sait avec une certitude à 100% que ce n'était pas intentionnel.

Le compilateur sait simplement que vous avez marqué le nom comme string et non string | number si vous voulez une valeur nullable, vous le marqueriez simplement comme ?string ou string | null ( ce qui est à peu près équivalent)

Je pense que l'implémentation qui correspondrait le mieux à nos exigences consiste à ne pas utiliser de types d'union, mais à suivre la suggestion d'origine :

wikiDate : ?Date ;

Nous sommes donc d'accord que les types sont non nuls par défaut et vous marqueriez nullable avec ? :) .
Notez qu'il s'agirait d'un type union puisque ?Date serait l'équivalent de Date | null | undefined :)

Oh désolé, j'essayais d'accepter nullable par défaut et non null avec une frappe spéciale (les symboles prêtent à confusion).

@fdecampredon , en fait, ce que cela signifie, c'est lorsqu'un champ ou une variable est marqué comme nullable, alors un type guard est requis pour l'accès :

var wikiDate: ?Date;

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

Ce n'est pas un changement radical, car nous devrions toujours pouvoir le faire :

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

Peut-être pensez-vous que nous ne pouvons pas avoir cela sans introduire null dans les types d'union ?

Votre premier exemple est tout à fait juste quand vous faites :

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

vous utilisez un typeguard et le compilateur ne doit rien avertir.

Cependant votre deuxième exemple n'est pas bon

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

le compilateur devrait avoir une erreur ici, un algorithme simple pourrait juste une erreur sur la première ligne (variable non initialisée non marquée comme nullable), un plus complexe pourrait essayer de détecter l'assignation avant la première utilisation :

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.

Je ne sais pas exactement où mettre la barrière, mais il faudrait certainement une sorte de réglage subtil pour ne pas gêner le développeur.

C'est un changement décisif et ne peut pas être introduit avant 2.0, sauf peut-être avec la directive 'use nonnull' proposée par @metaweta

Pourquoi ne pas avoir :

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

Et ayez un indicateur de compilateur (qui ne fonctionne que si -noimplicitany est activé) qui dit -noinferrednulls, ce qui désactive la syntaxe normale pour les types (comme string et int) et vous devez fournir un ? ou ! avec eux (null, undefined et tous les types étant des exceptions).

De cette manière, les non-nullables sont un opt-in, et vous pouvez utiliser l'indicateur du compilateur pour forcer un projet à être explicitement annulé.
Le compilateur signale les erreurs lors de l' affectation des types , pas après (comme la proposition précédente).

Les pensées?

Edit : j'ai écrit ceci parce que cela force l'idée d'utiliser non-null pour être explicite dans chaque action. Quiconque lit le code provenant de tout autre projet TS saura exactement ce qui se passe. De plus, le drapeau du compilateur devient très évident s'il est activé (car blah: string est une erreur, mais blah:!string ne l'est pas, de la même manière que -noimplicitany fonctionne).

Edit2 :
DefinatelyTyped pourrait alors être mis à niveau pour prendre en charge les non-inferrednulls, et ils ne changeront pas l'utilisation des bibliothèques si les gens choisissent de ne pas s'inscrire au ? et ! caractéristique.

Peu m'importe si non-null et non-undefined sont opt-in, opt-out, avec
des modificateurs de type (!?), une directive ou un indicateur de compilateur ; je ferai n'importe quoi
prend pour les obtenir _tant qu'ils sont possibles à exprimer_, ce qui n'est pas
actuellement le cas.

Le lundi 22 décembre 2014 à 14h35, Griffork [email protected] a écrit :

Pourquoi ne pas avoir :

var chaîne1 : chaîne ; // cela fonctionne comme le typescript le fait actuellement., n'a pas besoin de type-guarding avant utilisation, null et undefined peuvent lui être assignés.
chaîne1.longueur; //worksvar string2: !string; // cela ne fonctionne pas car la chaîne doit être affectée à une valeur non nulle et non définie, n'a pas besoin de type-guarding avant utilisation.var string3: ?string; //ceci doit être de type protégé sur non-null, non-indéfini avant use.var string4: !string|null = null; //Ceci peut être nul, mais ne doit jamais être indéfini (et doit être protégé par type avant utilisation).var string5: !string|undefined; //cela ne peut jamais être nul, mais peut être indéfini (et doit être protégé par type avant utilisation).

Et avoir un indicateur de compilateur (qui ne fonctionne que si -noimplicitany est activé) qui
dit -noinferrednulls, qui désactive la syntaxe normale pour les types (comme
string et int) et vous devez fournir un ? ou ! avec eux (nulle, indéfinie
et tout étant des exceptions).

De cette manière, les non-nullables sont un opt-in, et vous pouvez utiliser le compilateur
flag pour forcer l'annulation explicite d'un projet.
Le compilateur signale les erreurs lors de l'_affectation des types_, pas après (comme
la proposition précédente).

Les pensées?

Répondez directement à cet e-mail ou consultez-le sur GitHub
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 ce serait une option, mais une mauvaise à mon avis, et je vais vous expliquer pourquoi :

  • Cela entraînera beaucoup plus de travail dans les fichiers de définition, car nous devrons maintenant vérifier et annoter chaque type pour avoir la bonne définition.
  • Nous finirons par écrire !string (et parfois ?string ) partout sur le code, ce qui rendrait le code beaucoup moins lisible.
  • !string dans un système où les types sont nullables est un concept étrange, la seule façon dont vous ne pouvez pas vraiment le décrire est string minus null minus undefined , au contraire ?string est assez simple à décrire dans un système de types où les types sont null par défaut string | null | undefined .
  • Je prévois beaucoup de maux de tête (et de pertes de performances) pour trouver un algorithme de vérification de type où le compilateur comprend que string | null nécessite une protection de type mais que string ne le fait pas, vous introduisez essentiellement un concept où certains types d'union doivent être traités différemment des autres.
  • Et enfin le pire, nous perdons complètement l'inférence de type var myString = "hello" ce que myString devrait être string , ?string ou !string ? honnêtement un gros casse-tête en perspective ici.

Si nous n'avons pas de type non nul par défaut, la meilleure proposition que j'ai vue ici est la directive 'use non-null' proposée par @metaweta.
Bien sûr, il doit être bien spécifié, mais au moins avec une simple chaîne use non-null dans tout notre fichier, nous pouvons obtenir un comportement simple et prévisible.

@fdecampredon

  1. Cela peut être beaucoup plus de travail dans les fichiers de définition, mais _vous devriez quand même faire ce travail_ (pour vous assurer que les types sont corrects) et cette fois le compilateur vous rappellera ce que vous n'avez pas encore édité (si vous utilisez -noimplicitnull ).
  2. Je suis ouvert à d'autres suggestions d'annotation. Je crois honnêtement que le système de types actuel a sa place et ne devrait pas être _remplacé_. Je ne pense pas qu'un changement radical en vaille la peine. Au lieu de cela, je pense que nous devrions trouver une meilleure façon de décrire ce que vous recherchez. (Je n'aime vraiment pas l'idée de représenter tout cela avec des symboles, ils ne sont pas intuitifs.)
  3. Qu'est-ce qui est difficile à décrire à ce sujet ? J'ai vu des discussions ailleurs dans tapuscrit où cette demande (pour certains types) a été proposée (sans marqueur). J'ai fait une recherche rapide et je n'ai pas trouvé le problème, je chercherai plus tard.

  4. Si vous faites référence à ce que j'avais écrit comme !string|null , cela fonctionnerait dans le système actuel si null était traité _like_ {} (mais ne lui était pas attribuable).
    Si vous parlez de string|null que je n'avais pas dans ma liste, alors je pense que null devrait être ignoré dans ce cas. Null et undefined n'ont de sens que dans les unions où chaque non-null et non-undefined est précédé d'un ! et any n'est pas un any (cela pourrait être un avertissement/erreur du compilateur).
  5. Bonne question, et qui ne se pose que si vous utilisez l'option -noimplicitnull, je pense que l'option la plus sûre serait de l'affecter à l'option la plus susceptible de provoquer une erreur précoce (probablement nullable), mais j'obtiens le le sentiment qu'il y a une meilleure idée à laquelle je ne pense pas. Je me demande si quelqu'un d'autre a une suggestion sur la façon dont cela devrait être abordé?

Edit : Ajouté au point 2.
Edit : faute de frappe corrigée au point 4.

Cela peut être beaucoup plus de travail dans les fichiers de définition, mais vous devrez quand même faire ce travail (pour vous assurer que les types sont corrects) et cette fois le compilateur vous rappellera ce que vous n'avez pas encore modifié (si vous utilisez -noimplicitnull ).

Oui et non, vérifiez la bibliothèque que vous utilisez et voyez combien renvoie réellement null ou undefined.
c'est un cas assez rare, nous n'avons pu trouver dans ce numéro que très peu d'occurrences pour la bibliothèque standard et par exemple la bibliothèque Promise ne le fait jamais.
Mon point est que dans un système de type où le type n'est pas nullable par défaut, la plupart des fichiers de définition existants sont déjà valides .

Je suis ouvert à d'autres suggestions d'annotation. Je crois honnêtement que le système de type actuel a sa place et ne devrait pas être remplacé. Je ne pense pas qu'un changement radical en vaille la peine. Au lieu de cela, je pense que nous devrions trouver une meilleure façon de décrire ce que vous recherchez. (Je n'aime vraiment pas l'idée de représenter tout cela avec des symboles, ils ne sont pas intuitifs.)

Je ne pense pas qu'il existe un tel moyen mais j'espère me tromper, car il a une valeur inestimable à mon avis.
Pour la partie de rupture, pourquoi êtes-vous si contre la directive 'use non-null' ?
Les développeurs qui souhaitent un système de type nullable ne seraient pas du tout impactés (à moins qu'ils ajoutent déjà étrangement 'use non-null' en haut de leur fichier mais honnêtement ce serait un peu... bizarre)
Et les développeurs qui souhaitent un système de type non nul pourraient simplement utiliser la nouvelle directive.

Qu'est-ce qui est difficile à décrire à ce sujet ? J'ai vu des discussions ailleurs dans tapuscrit où cette demande (pour certains types) a été proposée (sans marqueur). J'ai fait une recherche rapide et je n'ai pas trouvé le problème, je chercherai plus tard.
Je trouve juste le concept un peu 'bizarre' et pas très clair, j'utilise généralement la composition comme outil principal de programmation, pas la _decomposition_ mais pourquoi pas.

Si vous faites référence à ce que j'avais écrit comme !string|null, cela fonctionnerait dans le système actuel si null était traité comme {} (mais ne lui était pas assignable). Si vous parlez de string|null que je n'avais pas dans ma liste, alors je pense que null devrait être ignoré dans ce cas. Null et undefined n'ont de sens que dans les unions où chaque non-null et non-undefined est précédé d'un ! et any n'est pas un any (cela pourrait être un avertissement/erreur du compilateur).
Je fais référence au fait que lorsque vous décomposez les 3 types :

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

Les 2 derniers n'ont pratiquement aucune différence, mais le compilateur doit savoir que pour string il ne doit pas forcer la vérification de type-guard et pour ?string il le devrait, cette information devra se propager partout, le L'algorithme sera beaucoup plus complexe qu'il ne l'est en réalité, et je suis presque sûr que je pourrais trouver des cas étranges avec l'inférence de type.

Bonne question, et qui ne se pose que si vous utilisez l'option -noimplicitnull, je pense que l'option la plus sûre serait de l'affecter à l'option la plus susceptible de provoquer une erreur précoce (probablement nullable), mais j'obtiens le le sentiment qu'il y a une meilleure idée à laquelle je ne pense pas. Je me demande si quelqu'un d'autre a une suggestion sur la façon dont cela devrait être abordé?

Ne le fera pas, cela introduirait fondamentalement le même problème que @RyanCavanaugh a commenté lorsque j'ai pensé à simplement introduire un indicateur qui permettrait de passer de null par défaut à null par défaut.
Dans ce cas, la première proposition était beaucoup plus simple.

Encore une fois pourquoi êtes-vous contre la directive 'use non-null' , plus j'y pense plus cela me semble la solution idéale.

@fdecampredon
Parce que "l'utilisation non nulle" telle qu'elle a été actuellement proposée change la façon dont la langue est _utilisée_ et non la façon dont la langue est _écrite_. Cela signifie qu'une fonction d'un emplacement lorsqu'elle est déplacée vers un autre emplacement peut fonctionner différemment lorsqu'elle est _utilisée_. Vous obtiendrez des erreurs au moment de la compilation qui se trouvent potentiellement dans 1 à 3 fichiers, car quelque chose est mal tapé.

La différence entre:
string
et
?string
Est-ce le ? et ! symbol demande une vérification de type stricte pour cette variable (un peu comme le serait votre directive "use nonnull", mais sur une base par variable). Expliquez-le de cette façon et je pense que vous n'aurez pas beaucoup de mal à ce que les gens le comprennent.

La différence est bien sûr :
Fichier 1 :

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

Fichier 2 :

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

Les développeurs doivent maintenant garder une carte mentale des fichiers non nuls et des fichiers non nuls. Je crois honnêtement que c'est une mauvaise idée (même si la plupart de _votre_ code est 'use nonnull').

Edit : De plus, lorsque vous commencez à taper une fonction et que vous obtenez la petite fenêtre qui vous indique quelle est la définition de la fonction, comment savez-vous si string dans cette définition est nullable ou non nullable ?

@Griffork

  • encore une fois c'est déjà le cas avec 'use strict' .
  • Au moins l'idée est simple. et introduire un concept simple dans le système de types.
  • Si un développeur déplace une fonction dans un autre fichier me semble un cas assez marginal, et comme l'erreur qui lui sera demandée concernera la vérification nulle, il pourra rapidement comprendre ce qui se passe ...

Encore une fois, ce n'est pas parfait, mais je ne vois pas de meilleure alternative.

Juste pour être clair, vos seuls problèmes avec ce que j'ai proposé (après les contre-arguments) que je peux voir sont :

  • Nous ne savons pas quel serait le comportement de var mystring = "string"
  • Vous ne voulez pas avoir à taper des symboles partout.

Et mes préoccupations concernant la directive sont :

  • Les non-nulls ne seraient pas explicites (mais peuvent se produire dans le même projet que les nullables). edit : correction du libellé pour plus de sens.
  • Il sera plus difficile pour les développeurs de savoir si ce qu'ils écrivent est nullable ou non.
  • Les définitions de fonction que vous voyez lorsque vous appelez une fonction ( edit : la fenêtre contextuelle fournie par Visual Studio lorsque vous commencez à taper une fonction) peuvent ou non être nullables et vous ne seriez pas en mesure de le dire.
  • Une fois qu'une fonction est enveloppée dans 2 ou 3 couches, vous ne savez pas si sa définition est toujours correcte (car vous ne pouvez pas utiliser l'inférence de type à travers des fichiers qui n'ont pas "use nonnull").

Honnêtement, la mise en œuvre de "use strict" n'est pas quelque chose à laquelle il faut aspirer . Il a été conçu pour JavaScript (pas pour Typescript), et en JavaScript, il existe peu d'alternatives précieuses. Nous utilisons Typescript, nous avons donc la possibilité de mieux faire les choses.
Et je ne vois pas pourquoi la directive vaut mieux que de forcer les développeurs à être explicites sur leurs intentions (après tout, c'est la raison pour laquelle Typescript a été créé, n'est-ce pas ?).

Edit : Clarification d'un point.
Edit : mettre la chaîne entre guillemets

Honnêtement, votre résumé de ma préoccupation est un peu petit et ne reflète pas ce que j'ai écrit, votre proposition ajoute de la complexité au système de types, rend l'algorithme de vérification de type un casse-tête, est assez difficile à spécifier avec tous les cas extrêmes. créer spécialement pour l'inférence de type et rendre le code ultra-verbeux pour rien. Fondamentalement pas le bon outil pour le travail.

Je veux que tous les non-nulls soient explicites (car ils peuvent se produire dans le même projet que les nullables).

Je veux le fait que string soit en fait string | null | undefined sans vous forcer à vérifier cela, explicite.

Il sera plus difficile pour les développeurs de savoir si ce qu'ils écrivent est nullable ou non.

Je doute qu'il y ait un seul fichier dans un projet sans "utiliser non nul" si le projet utilise la non-nullité, et le défilement en haut de votre fichier n'est pas si difficile (au moins lorsque vous écrivez un fichier avec moins de 500 lignes de code ce qui est la majorité des cas...)

Les définitions de fonction que vous voyez lorsque vous appelez une fonction peuvent ou non être nullables et vous ne seriez pas en mesure de le dire.

Oui, vous le ferez s'il provient d'un module qui a une directive 'use non-null', ce qui sera tapé en conséquence, si vous ne considérez pas tout comme nullable ...

Une fois qu'une fonction est enveloppée dans 2 ou 3 couches, vous ne savez pas si sa définition est toujours correcte (car vous ne pouvez pas utiliser l'inférence de type à travers des fichiers qui n'ont pas "use nonnull").

Je ne comprends pas votre point ici.

Honnêtement, la mise en œuvre de "use strict" n'est pas quelque chose à laquelle il faut aspirer. Il a été conçu pour JavaScript (pas pour Typescript), et en JavaScript, il existe peu d'alternatives précieuses. Nous utilisons Typescript, nous avons donc la possibilité de mieux faire les choses.
Et je ne vois pas pourquoi la directive vaut mieux que de forcer les développeurs à être explicites sur leurs intentions (après tout, c'est la raison pour laquelle Typescript a été créé, n'est-ce pas ?).

TypeScript est un sur-ensemble de javascript, il a sa racine dans JavaScript et le but est de vous permettre d'écrire du javascript de manière plus sûre, donc réutiliser un concept de JavaScript ne semble pas déraisonnable.

Et je ne vois pas pourquoi la directive vaut mieux que de forcer les développeurs à être explicites sur leurs intentions

Parce que vous ne pourrez tout simplement pas obtenir le même résultat, pour toutes les raisons que j'ai citées, avoir un type non nullable dans un système de type nullable n'est qu'une mauvaise idée.

De mon point de vue il y a 3 solution viable ici :

  • Changements de rupture pour 2.0 où les types deviennent non nullables
  • Indicateur du compilateur qui passe par défaut au type non nullable comme j'ai proposé quelques dizaines de commentaires, mais @RyanCavanaugh avait un bon point à ce sujet. (même si je pense honnêtement que ça en vaut la peine)
  • 'utiliser une directive non nulle'

Le drapeau en vaudrait la peine pour moi, d'ailleurs.

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

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

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

Si les fonctions sont la seule préoccupation, une exception pourrait être faite dans les cas où une surcharge contient un argument explicite null - il aurait toujours la priorité sur null implicite (afin d'être harmonieux avec --noImplicitNull ) . Ce serait aussi l'interprétation qui aurait du sens pour l'utilisateur (c'est-à-dire "ce que je dis explicitement devrait l'emporter sur ce qui est dit implicitement"). Bien que je me demande s'il existe d'autres problèmes similaires avec le drapeau qui ne peuvent pas être résolus de cette façon. Et bien sûr, cela ajoute une certaine complexité à la fois à la spécification et à l'implémentation :|

  1. string|null|undefined était explicite dans ma proposition en utilisant le drapeau, c'est pourquoi je l'ai omis.
  2. Alors pourquoi l' avoir par fichier ? Je suggère ma suggestion car elle _ne rompt pas la compatibilité descendante_ ce qui est important pour moi, et probablement pour beaucoup d'autres développeurs. Et il peut être forcé d'être à l'échelle du projet.
  3. J'utilise beaucoup de fichiers que je ne crée pas ; comment saurais-je que ce fichier particulier a « use nonnull » ? Si j'ai 100 fichiers créés par d'autres personnes, je dois mémoriser lequel de ces 100 fichiers n'est pas nul ? (ou, _chaque fois_ j'utilise une variable/fonction d'un autre fichier, je dois ouvrir ce fichier et le vérifier ?)
  4. Voyons si je peux rendre cela plus clair : exemple à la fin du post.
  5. Où t'arrêtes-tu ? Où l'appelez-vous mauvais? Une chaîne ou un mot-clé au début d'un fichier ne doit pas modifier le comportement d'un fichier. "use strict" a été ajouté à javascript car

    1. Aucun grand super-ensemble de Javascript (par exemple Typescript) n'existait à l'époque qui pouvait faire ce qu'il voulait.

    2. C'était une tentative d' accélérer le traitement de JavaScript (ce qui à mes yeux est la seule raison pour laquelle c'est excusable).

      "use strict" a été adopté _non pas parce que c'était la bonne chose à faire_ mais parce que c'était la seule façon pour les navigateurs de répondre aux demandes des développeurs. Je détesterais voir dactylographié ajouter 1 (puis 2, puis 3 puis 4) d'autres directives qui _changent fondamentalement le fonctionnement du langage_ en tant que chaînes déclarées dans une portée arbitraire et affectent d'autres portées arbitraires. C'est vraiment une mauvaise conception de langage. Je serais heureux si "use strict" n'existait pas dans Typescript, et était à la place un indicateur de compilateur (et Typescript l'émettait dans chaque fichier/portée qui en avait besoin).

  6. Javascript n'est pas un "système de type non-nullable", Typescript n'est pas un "système de type non-nullable". L'introduction de la possibilité de déclarer des types non nullables ne signifie pas que l'ensemble du système est « non nullable ». Ce n'est pas le but de Typescript.
File 1:
"use notnull"
export string foo() {
    return "mygeneratedstring";
}
File 2:
export string foo() {
    return file1.foo()
}
File 3:
"use notnull"
file2.foo(); //???

Vous avez en fait la possibilité de perdre des informations contextuelles tout en utilisant la même syntaxe ! Ce n'est pas quelque chose que j'ai hâte d'avoir dans n'importe quelle langue que j'utilise.


On a l'impression de tourner en rond. Si je n'ai pas de sens pour vous, je suis désolé, mais vous semblez soulever à plusieurs reprises les mêmes problèmes, et j'y réponds à plusieurs reprises.

@spion
Dans cet exemple, la chaîne n'est pas une valeur nulle implicite (je pense que cela suppose que l'indicateur --noImplicitNull est activé)

string|null|undefined était explicite dans ma proposition en utilisant le drapeau, c'est pourquoi je l'ai laissé

Ce que je veux dire, c'est que dans le système actuel, var myString: string est implicitement var myString: (string | null | undefined); et qu'en plus, le compilateur ne vous oblige pas à utiliser typeguard à moins que tous les autres types d'union.

Alors pourquoi l'avoir par fichier ? Je suggère ma suggestion car elle ne rompt pas la compatibilité descendante, ce qui est important pour moi, et probablement pour beaucoup d'autres développeurs. Et il peut être forcé d'être à l'échelle du projet.
La directive ne rompt pas la compatibilité descendante (à moins que vous n'utilisiez un littéral de chaîne 'use not-null' à la position d'une directive pour une raison assez étrange, ce que personne ne fait, je parie), également à un moment donné, TypeScript devra rompre la compatibilité descendante, c'est pourquoi sever a défini la version majeure, mais c'est une autre histoire.

J'utilise beaucoup de fichiers que je ne crée pas ; comment saurais-je que ce fichier particulier a « use nonnull » ? Si j'ai 100 fichiers créés par d'autres personnes, je dois mémoriser lequel de ces 100 fichiers n'est pas nul ? (ou, chaque fois que j'utilise une variable/fonction d'un autre fichier, je dois ouvrir ce fichier et le vérifier ?)

Si vous êtes dans un projet avec des lignes directrices etc etc comme si chaque projet est organisé normalement vous savez... dans le pire des cas il suffit de faire défiler un peu... ça ne semble pas si difficile...

Où t'arrêtes-tu ? Où l'appelez-vous mauvais? Une chaîne ou un mot-clé au début d'un fichier ne doit pas modifier le comportement d'un fichier. "use strict" a été ajouté à javascript car
Aucun grand super-ensemble de Javascript (par exemple Typescript) n'existait à l'époque qui pouvait faire ce qu'il voulait.
C'était une tentative d'accélérer le traitement de JavaScript (ce qui à mes yeux est la seule raison pour laquelle c'est excusable). "use strict" a été adopté non pas parce que c'était la bonne chose à faire, mais parce que c'était la seule façon pour les navigateurs de répondre aux demandes des développeurs. Je détesterais voir dactylographié ajouter 1 (puis 2, puis 3 puis 4) d'autres directives qui modifient fondamentalement le fonctionnement du langage sous forme de chaînes déclarées dans une portée arbitraire et affectant d'autres portées arbitraires. C'est vraiment une mauvaise conception de langage. Je serais heureux si "use strict" n'existait pas dans Typescript, et était à la place un indicateur de compilateur (et Typescript l'émettait dans chaque fichier/portée qui en avait besoin).

'use strict' a été introduit pour changer le comportement du langage et maintenir la compatibilité rétro, exactement le même problème auquel nous sommes confrontés maintenant, et disparaîtra plus ou moins avec les modules es6 (et donc use not null pourrait disparaître avec la prochaine version majeure de tapuscrit) .

Javascript n'est pas un "système de type non-nullable", Typescript n'est pas un "système de type non-nullable". L'introduction de la possibilité de déclarer des types non nullables ne signifie pas que l'ensemble du système est « non nullable ». Ce n'est pas le but de Typescript.

Ces phrases n'ont aucun sens pour moi, JavaScript n'a pas de système de type statique, donc comme je l'ai dit, le fait que vous puissiez affecter null à une variable qui était avant string peut être facilement comparé au fait que vous pouvez affecter 5 à une variable qui était une chaîne auparavant.
La seule différence est que TypeScript, un vérificateur de type créé pour JavaScript, considère avec opiniâtreté null comme assignable à tout et non 5 .

Pour votre exemple, oui, nous perdons des informations entre les fichiers, c'est un compromis acceptable pour moi mais je peux comprendre votre inquiétude.

On a l'impression de tourner en rond. Si je n'ai pas de sens pour vous, je suis désolé, mais vous semblez soulever à plusieurs reprises les mêmes problèmes, et j'y réponds à plusieurs reprises.

Oui, je suis d'accord, je doute qu'il y ait un consensus sur celui-là, je pense honnêtement que je finirai par bifurquer le compilateur et ajouter un type non nul pour moi-même en espérant qu'un jour il finira dans le compilateur principal, ou ce flux suffisamment mature pour être utilisable.

@Griffork en passant, je pense que l' idée string étant un nullable implicite number | null prendrait toujours la prédominance sur la première surcharge, donc dans les deux cas foo(null) sera string .

@RyanCavanaugh pensez -vous que cela pourrait fonctionner ?

@fdecampredon

  1. "use strict" a été introduit pour optimiser le javascript. Il devait être consommé par les navigateurs , donc il devait être dans le code. Et il devait être rétrocompatible, donc il devait s'agir d'une déclaration non fonctionnelle.
    Les types non nullables n'ont pas à être consommés par les navigateurs, ils n'ont donc pas besoin d'avoir une directive dans le code.
  2. Désolé, je faisais référence à quelque chose que j'avais oublié de poster, le voici :

Selon Facebook : "En JavaScript, null convertit implicitement en tous les types primitifs ; c'est aussi un habitant valide de tout type d'objet."
Le raisonnement pour moi de vouloir que non-nullable soit explicite (dans chaque action qu'un développeur fait) est parce qu'en utilisant non-nullable, le développeur reconnaît qu'il s'écarte de la façon dont javascript fonctionne, et qu'un type étant non-nullable n'est pas garanti (car il y a beaucoup de choses qui peuvent causer des problèmes).
Une propriété non nullable est une propriété qui ne peut

J'utilise Typescript depuis assez longtemps. Au départ, il m'a été difficile de convaincre mon patron de passer de Javascript à Typescript, et c'était assez difficile pour moi de le convaincre de nous laisser mettre à niveau chaque fois qu'il y avait un changement de rupture (même s'il n'y en a pas, ce n'est pas facile). Lorsque ES:Harmony sera pris en charge par les navigateurs, nous souhaiterons probablement l'utiliser. Si un changement décisif se produit dans Typescript entre maintenant et lorsque Typescript prend en charge ES:Harmony (en particulier un aussi répandu que non null capable), je doute que je gagnerais cet argument. Sans parler du temps et de l'argent qu'il nous en coûterait pour recycler tous nos développeurs (je suis notre "personne de Typescript", c'est mon travail de former nos développeurs Typescript).
Ainsi, je suis vraiment contre l'introduction de changements de rupture.
Cela ne me dérangerait pas de pouvoir utiliser moi-même des non-nullables, s'il y avait un moyen de les introduire dans le futur code sans casser l'ancien code (c'est la raison de ma proposition). Idéalement, tout notre code serait finalement converti, mais cela nous coûterait beaucoup moins de temps et d'argent pour former des personnes et utiliser des bibliothèques tierces.

J'aime l'idée d'utiliser des symboles (selon ma suggestion) au lieu d'une directive par fichier, car ils ont la capacité de se propager sans perte contextuelle.

@Griffork Ok, comme je l'ai dit, je comprends votre inquiétude, j'espère que vous comprenez la mienne.
Maintenant, je n'ai pas le même avis, la technologie change, rien n'est stable (tout le drame autour d'angular 2 l'a prouvé), et je préfère casser des choses et avoir de meilleurs outils que de m'en tenir à ce que j'ai, je pense que dans sur le long terme cela me fait gagner du temps et de l'argent.
Maintenant, comme nous l'avons tous les deux conclu plus tôt, je doute que nous ayons un consensus ici, je préfère la directive, vous préférez votre proposition.
Quoi qu'il en soit comme je l'ai dit pour ma part je vais juste essayer de bifurquer le compilateur et d'ajouter un type non null, car je pense que ce n'est pas un gros changement dans le compilateur.
Merci pour la discussion c'était intéressant

@fdecampredon

Merci pour la discussion c'était

En supposant que vous vouliez éclairer (des travaux intéressants aussi :D), j'ai aussi apprécié le débat :).
Bonne chance avec votre fork, et j'espère que nous aurons une personne TS ici pour au moins rejeter certaines de nos suggestions (afin que nous sachions ce qu'ils ne veulent certainement pas mettre en œuvre).

Donc, pour élaborer davantage sur ma solution proposée au problème décrit par @RyanCavanaugh , après avoir ajouté le type null et le drapeau --noImplicitNull , le typechecker lorsqu'il est exécuté sans le drapeau aura un comportement modifié :

Chaque fois qu'une surcharge de type contient |null (comme dans l'exemple donné) :

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

le vérificateur de types prendra chaque surcharge et créera de nouvelles variantes avec (implicite) null à cette position d'argument, ajoutée à la fin de la liste :

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;

Cela donne la même interprétation que la version stricte de --noImplicitNull : lorsqu'elle est passée à null, la première surcharge correspondante est la number|null explicite. En fait, cela rend les valeurs nulles explicites plus fortes que les valeurs implicites.

(Oui, je sais que le nombre de définitions implicites augmente de façon exponentielle avec le nombre d'arguments qui ont des surcharges de |null - j'espère qu'il existe une implémentation plus rapide qui "autorise" les valeurs NULL à un deuxième passage s'il n'y a pas d'autres types correspond, mais je ne pense pas que ce sera un problème de toute façon car je m'attends à ce que cette situation soit rare)

Formulé comme ceci, je pense que le changement sera, espérons-le, entièrement rétrocompatible et opt-in.

Voici quelques idées supplémentaires sur le reste du comportement :

L'attribution de valeurs nulles ou de valeurs non initialisées sera également autorisée pour les valeurs de tout type lorsque l'indicateur n'est pas passé.

Lorsque l'indicateur est activé, le fait de laisser les valeurs non initialisées (non affectées) ou nulles est autorisé jusqu'au moment où elles sont transmises à une fonction ou à un opérateur qui s'attend à ce qu'elles ne soient pas nulles. Pas sûr des membres du groupe : ils le feraient non plus

  • doit être initialisé à la fin du constructeur, ou
  • l'initialisation peut être suivie en vérifiant si les méthodes membres qui les initialisent complètement ont été appelées.

Les unions nulles explicites se comporteront de la même manière que toutes les autres unions dans les deux modes. Ils auraient besoin d'un gardien pour restreindre le type de syndicat.

Je viens de lire _tous_ les commentaires ci-dessus et c'est beaucoup. C'est décevant qu'après tant de commentaires et 5 mois, nous débattions toujours des mêmes points : la syntaxe et les drapeaux...

Je pense que beaucoup de gens ont avancé des arguments solides en faveur de la valeur de la vérification statique des valeurs nulles par le compilateur dans les grandes bases de code. Je ne dirai rien de plus (mais +1).

Jusqu'à présent, le débat principal a porté sur la syntaxe et le problème de compatibilité avec des tonnes de codes TS existants et de définitions de bibliothèques TS. Je peux voir pourquoi introduire un type nul et la syntaxe Flow ?string semble le plus propre et le plus naturel. Mais cela change la signification de string et est donc un _énorme_ changement pour _tout_ le code existant.

Les solutions proposées ci-dessus pour ce changement décisif consistent à introduire des indicateurs de compilateur ( expliqué pourquoi c'est une idée terrible) ou une directive par fichier. Cela semble être une mesure palliative très hackish. De plus, je détesterais lire du code au milieu d'une revue de diff ou d'un gros fichier et de ne pas pouvoir immédiatement connaître _à coup sûr_ la signification du code que je regarde. 'use strict'; été cité comme précédent. Ce n'est pas parce que TS a hérité des erreurs du passé de JS qu'il faut les répéter.

C'est pourquoi je pense que l'annotation "not null" ( string! , !string , ...) est la seule solution et je vais essayer d'introduire un nouvel argument dans la discussion : pas si mal.

Personne ne veut avoir une frange ( ! ) partout dans le code. Mais nous n'avons probablement pas à le faire. TS en déduit beaucoup sur la dactylographie.

1. Variables locales
Je ne m'attends pas à annoter mes variables locales avec des bangs. Peu m'importe si j'affecte null à une variable locale, tant que je l'utilise de manière sûre, ce que TS est parfaitement capable d'affirmer.

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

Assez souvent, j'utilise un initialiseur au lieu d'un type.

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

2. Valeurs de retour de fonction
Très souvent je ne précise pas la valeur de retour. Tout comme TS déduit le type, il peut en déduire s'il est nul ou non.

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

EDIT : mauvaise idée à cause de l'héritage et des variables de fonction, voir le commentaire de @Griffork ci-dessous
Evénement si je donne un type de retour, TS est parfaitement capable d'ajouter l'annotation "not null" pour moi.

Si vous voulez remplacer la nullabilité inférée (par exemple parce qu'une classe dérivée peut retourner null bien que votre méthode ne le fasse pas), vous spécifiez le type explicitement :

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

3. Paramètres de fonction
Tout comme vous devez spécifier explicitement un type, c'est là que vous _devez_ indiquer si vous n'acceptez pas les paramètres nuls :

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

Pour éviter les erreurs de compilation dans les anciennes bases de code, nous avons besoin d'un nouveau drapeau "Le déréférencement nul est une erreur", tout comme nous avons "Aucun implicite". _Ce drapeau ne change pas l'analyse du compilateur, seulement ce qui est signalé comme une erreur !_
Est-ce beaucoup de franges à ajouter ? Difficile à dire sans faire de statistiques sur des bases de code réelles et volumineuses. N'oubliez pas que les paramètres facultatifs sont courants dans JS et qu'ils sont tous nullables, vous auriez donc besoin de ces annotations si 'not null' était la valeur par défaut.
Ces franges sont également de bons rappels que null n'est pas accepté pour les appelants, conformément à ce qu'ils savent à propos de string aujourd'hui.

4. Champs de classe
Ceci est similaire à 3. Si vous devez lire un champ sans pouvoir en déduire son contenu, vous devez le marquer comme non null lors de la déclaration.

class C {  x: string!; }

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

Nous pourrions imaginer un raccourci pour initialiser, peut-être :

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

Un raccourci similaire est probablement souhaitable pour les propriétés nullables si vous dites que not-null est la valeur par défaut avec un initialiseur.
Encore une fois, il est difficile de dire si la plupart des champs du code existant sont nullables ou non, mais cette annotation est tout à fait conforme aux attentes des développeurs aujourd'hui.

5. Déclarations, .d.ts
L'énorme avantage est que toutes les bibliothèques existantes fonctionnent. Ils acceptent des valeurs nulles et non nulles comme entrées et sont supposés renvoyer une valeur éventuellement nulle.
Au début, vous devrez convertir explicitement certaines valeurs de retour en "non null", mais la situation s'améliorera à mesure que les définitions sont mises à jour lentement pour indiquer quand elles ne retournent jamais null. L'annotation des paramètres est plus sûre mais n'est pas obligatoire pour une compilation réussie.

Je pense qu'indiquer l'état "non nul" d'une valeur où le compilateur ne peut pas en déduire peut être utile (par exemple, lors de la manipulation de code non typé, ou lorsque le développeur peut faire des affirmations concernant l'état global du programme) et un raccourci ça peut être sympa :

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().

Je pense que la possibilité d'indiquer au compilateur - de manière simple - que quelque chose n'est pas nul est souhaitable. C'est également vrai pour non-null par défaut, bien qu'il soit plus difficile de trouver une syntaxe simple et logique.

Désolé, c'était un très long post. J'ai essayé de démontrer l'idée que même avec "null par défaut", nous n'aurions pas à annoter tout notre code : TS peut déduire l'exactitude de la plupart des codes sans notre aide. Les deux exceptions sont les champs et les paramètres. Je pense que ce n'est pas excessivement bruyant et permet une bonne documentation. Un point fort en faveur de cette approche est qu'elle est rétrocompatible.

Je suis d'accord avec la plupart de ce que vous avez dit @jods4 mais j'ai quelques questions et préoccupations :

2) si le compilateur peut automatiquement convertir nullable en non nullable sur les types de retour, comment pouvez-vous remplacer cela (par exemple pour l'héritage ou les fonctions qui sont remplacées) ?

3 & 5) Si le compilateur peut convertir nullable en non nullable, une signature de fonction dans un fichier de code peut désormais se comporter différemment de la même signature de fonction dans un fichier d.ts.

6) À quoi ressemble le bang lors de l'utilisation de types d'union ?

2) Bonne prise. Je ne pense pas que cela fonctionnerait bien avec l'héritage (ou les "délégués") en effet.
Là où je travaille, notre expérience avec TS est que nous ne tapons presque jamais explicitement les valeurs de retour des fonctions. TS les comprend et je pense que TS peut comprendre la partie nulle/non nulle.

Les seuls cas où nous les spécifions explicitement sont lorsque nous voulons retourner une interface ou une classe de base, par exemple pour l'extensibilité (y compris l'héritage). La nullité semble correspondre assez bien à cette description.

Modifions ma proposition ci-dessus : une valeur de retour explicitement typée n'est _pas_ déduite non nullable si elle n'est pas déclarée ainsi. Cela fonctionne bien pour les cas que vous proposez. Il fournit un moyen de remplacer le compilateur en déduisant « non nul » sur un contrat qui pourrait être nul dans les classes enfants.

3 & 5) Avec le changement ci-dessus, ce n'est plus le cas, non ? La signature des fonctions dans le code et les définitions se comportent désormais exactement de la même manière.

6) Question intéressante !
Celui-ci n'est pas génial en effet, surtout si vous ajoutez des génériques au mélange. La seule issue à laquelle je puisse penser est que toutes les parties doivent être non nulles. Vous avez probablement besoin d'un littéral de type nul de toute façon à cause des génériques, voir mon dernier exemple.

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

Pour les besoins de la discussion, notez que si vous optiez pour des types non nuls par défaut, vous devrez également inventer une nouvelle syntaxe : comment exprimer qu'un type générique ne doit pas autoriser les valeurs nullables ?
Et inversement, comment spécifieriez-vous que même si le type générique T contient null, une fonction ne renvoie jamais T mais jamais null ?

Les littéraux de type sont corrects mais pas très satisfaisants non plus : var x: { name: string }! , surtout s'ils devaient s'étendre sur plus d'une ligne :(

Plus d'exemples :
Tableau de nombres non nuls number![]
Tableau de nombres non nul : number[]!
Rien ne peut être nul : number![]!
Génériques : Array<number!>[]

2, 3 & 5) Je suis d'accord avec la modification de la proposition, cela semble fonctionner.
6)

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

Je suppose qu'ici vous voulez dire que f peut retourner null, pas que f peut être assigné à null.

Pour les syndicats, que pensez-vous de la syntaxe suivante comme autre option disponible ? (ça a l'air un peu idiot, mais pourrait être plus succinct/plus facile à suivre dans certains cas).

var x = ! & number | string;

Pour signifier que ni l'un ni l'autre n'est nullable.


Pour les littéraux de type, je dirais que vous pouvez mettre le bang au début de l'expression littérale ainsi qu'à la fin, car var x: !{name: string} , sera à mon avis plus facile à utiliser que de l'avoir à la fin.

@ jods4 avez-vous lu sur mon correctif proposé « null explicite a priorité sur nul implicite » ? Cela résout le problème de sémantique variable, je pense.

@Griffork
Oui, bien sûr, « est nullable » était une mauvaise formulation pour « peut renvoyer null ».

Ce qui me fait penser que function f() {} est en fait une déclaration de f , qui peut être définie plus tard... voulons-nous exprimer que f ne sera jamais défini sur null ? Comme function f!() {} ? Cela semble aller trop loin à mon avis. Je pense que le vérificateur de type devrait supposer que c'est toujours le cas. Il existe d'autres cas marginaux rares qu'il ne peut de toute façon pas gérer.

En ce qui concerne la nouvelle option, je suis d'accord pour dire qu'elle a l'air un peu idiote et introduit un symbole supplémentaire dans la déclaration de type : & , avec un seul objectif... Cela ne me semble pas une bonne idée. Et dans plusieurs langues, & AND se lie avec une priorité plus élevée que | OR ce qui n'est pas ce qui se passe ici.

Mettre le bang devant les types est une alternative possible, je suppose que nous devrions essayer de réécrire tous les exemples pour voir ce qui pourrait être mieux. Je pense que ce raccourci de champ que j'ai suggéré va être un problème :

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
Oui je l'ai vu.

Tout d'abord, je ne suis pas sûr que cela résolve vraiment le problème. Donc, avec votre nouvelle définition, quelle surcharge se lie à fn(null) ? Celui « généré » qui renvoie number ? N'est-ce pas incohérent avec le fait que la seconde surcharge accepte également null mais renvoie string ? Ou s'agit-il d'une erreur du compilateur car vous ne pouvez pas résoudre les surcharges ?

Deuxièmement, je suis presque sûr que nous pouvons trouver beaucoup d'autres problèmes. Je pense que changer la signification globale du code source en appuyant simplement sur un commutateur ne peut tout simplement pas fonctionner. Non seulement dans .d.ts , mais les gens adorent réutiliser le code (réutilisation de bibliothèque ou même copier-coller). Votre suggestion est que la signification sémantique de mon code source dépend du drapeau du compilateur et cela me semble une proposition très imparfaite.

Mais si vous pouvez prouver que le code sera compilé et exécuté correctement dans tous les cas, je suis tout à fait d'accord !

@jods4

Dans les deux cas la 2ème surcharge sera utilisée

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

car le 2ème null explicite aura la priorité sur l'implicite (le premier), quel que soit le drapeau utilisé. Par conséquent, la signification ne change pas en fonction du drapeau.

Étant donné que le type null n'existe pas actuellement, ils seraient introduits ensemble et le changement serait entièrement rétrocompatible. Les anciennes définitions de type continueront de fonctionner normalement.

@spion
Alors, quel était le problème avec le function fn(x: null): number; implicite ? Si la 2ème surcharge a toujours la priorité, quel que soit le drapeau utilisé, tout ce "correctif" n'a aucun effet?

Autre question : aujourd'hui, toutes les définitions de lib ont un comportement « null autorisé ». Donc, jusqu'à ce qu'ils soient _tous_ mis à jour, je dois utiliser le drapeau "implicitement nul". Maintenant, avec ce drapeau activé, comment puis-je déclarer une fonction qui prend un paramètre non nul dans mon projet ?

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

Le correctif a certainement un effet. Cela rend le comportement avec et sans le drapeau cohérent, ce qui résout le problème sémantique mentionné ci-dessus.

Deuxièmement, vous n'aurez pas à utiliser un indicateur "null par défaut" pour les bibliothèques tierces. La chose la plus importante à propos de cette fonctionnalité est que vous n'aurez rien à faire pour obtenir les avantages dans la grande majorité des cas.

Disons qu'il existe une bibliothèque qui calcule le nombre de mots d'une chaîne. Sa déclaration est

declare function wordCount(s: string): number;

Disons que votre code utilise cette fonction de cette façon :

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

Ce programme passe sous les deux compilateurs : même si les nulls implicites sont désactivés. Le code est totalement rétrocompatible.

Pourquoi? Parce que le compilateur tel qu'il est aujourd'hui a déjà la foi que les valeurs ne sont pas nulles partout où vous essayez de les utiliser (même si elles peuvent théoriquement être nulles).

Le nouveau compilateur supposera également que les valeurs ne sont pas nulles lorsque vous essayez de les utiliser. Il n'a aucune raison de croire le contraire, puisque vous n'avez pas spécifié que la valeur peut être nulle, de sorte qu'une partie du comportement reste la même. Le changement de comportement ne prend effet que lors de l'affectation de null (ou en laissant les variables non initialisées) et en essayant ensuite de transmettre ces valeurs à une fonction comme ci-dessus. Ils ne nécessitent aucune modification dans les autres parties du code qui utilisent normalement des valeurs.

Voyons maintenant l'implémentation de wordCount

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

Oups, ce type ne raconte pas toute l'histoire. Il est possible que cette fonction renvoie une valeur nulle.

Le problème, c'est justement ça. Il est impossible de raconter toute l'histoire dans le compilateur actuel, même si nous le voulions. Bien sûr, nous disons que la valeur de retour est un nombre, ce qui indique implicitement qu'elle peut être nulle : mais le compilateur n'en avertit jamais lorsque nous essayons d'accéder de manière incorrecte à cette valeur potentiellement nulle. Il pense toujours avec plaisir que c'est un nombre valide.

Après le changement noImplicitNull , nous obtiendrons ici exactement le même résultat si nous utilisons les mêmes définitions de type. Le code se compilera sans plaintes. Le compilateur pensera toujours avec plaisir que wordCount renvoie toujours un nombre. Le programme peut toujours échouer si nous passons des chaînes vides, comme avant (l'ancien compilateur ne nous avertira pas que le nombre renvoyé peut être nul, et le nouveau non plus car il fait confiance à la définition de type). Et si nous voulons le même comportement, nous pouvons le conserver sans rien changer dans notre code. (1)

Cependant, maintenant, nous _pourrons_ faire mieux : nous pourrons écrire une définition de type améliorée pour wordCount :

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

et obtenons un avertissement du compilateur chaque fois que nous essayons d'utiliser wordCount sans rechercher une valeur de retour nulle.

La meilleure partie est que cette amélioration est complètement facultative. Nous pouvons conserver toutes les déclarations de type telles qu'elles étaient et obtenir à peu près exactement le même comportement que maintenant. Les déclarations de type ne s'aggraveront pas (2) - elles ne peuvent être améliorées pour être plus précises dans les cas où nous pensons que cela est nécessaire.


(1) : il y a déjà une amélioration ici même sans améliorer la définition du type. avec les nouvelles définitions de type, vous ne pourrez pas passer accidentellement null à sumWordcounts et obtenir une exception de pointeur null lorsque la fonction wordCount essaiera de .split() ce null.

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

(2) : ok, c'est un mensonge. Les déclarations de type vont empirer pour un petit sous-ensemble de fonctions : celles qui prennent des arguments nullables qui ne sont pas des arguments facultatifs.

Les choses iront toujours bien pour les arguments facultatifs :

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

mais pas pour les arguments qui ne sont pas facultatifs et peuvent être nuls

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

Je dirais que ces fonctions sont assez rares et que les correctifs nécessaires seront minimes.

@spion

Le correctif a certainement un effet. Cela rend le comportement avec et sans le drapeau cohérent.

De quelle manière pouvez-vous donner un exemple complet ? Vous avez également dit :

Dans les deux cas la 2ème surcharge sera utilisée

Ce qui, pour moi, implique que le correctif n'a aucun effet ?

Ce programme passe sous les deux compilateurs : même si les nulls implicites sont désactivés. Le code est totalement rétrocompatible.

Oui il compile sans erreur dans les deux cas, mais pas au même résultat. sumWordCounts() sera tapé comme number! dans un cas et number? dans le second. Changer la sémantique du code avec un commutateur n'est _pas_ hautement souhaitable. Comme démontré précédemment, ce changement pourrait alors avoir des effets globaux, par exemple sur la résolution des surcharges.

Le nouveau compilateur supposera également que les valeurs ne sont pas nulles lorsque vous essayez de les utiliser. Il n'y a aucune raison de croire le contraire

Non! C'est tout l'intérêt : je veux que le compilateur me renvoie une erreur lorsque j'utilise une valeur potentiellement nulle. S'il "suppose" non nul comme je le fais quand je code, alors cette fonctionnalité est inutile !

Le changement de comportement ne prend effet que lors de l'affectation de null (ou en laissant les variables non initialisées) et en essayant ensuite de transmettre ces valeurs à une fonction comme ci-dessus.

Je ne suis pas sûr de comprendre, car "fonction comme ci-dessus" accepte en fait les valeurs nulles ...

En lisant la fin de votre dernier commentaire, j'ai l'impression que nous n'obtiendrons pas de véritable analyse statique nulle tant que nous n'allumerons pas le commutateur, ce que vous ne pourrez pas faire tant que _toutes_ vos définitions de bibliothèque ne seront pas corrigées. :(

De manière générale, il est difficile de comprendre pleinement tous les cas et comment tout fonctionne avec de simples mots. Quelques exemples pratiques seraient très utiles.

Voici un exemple et comment j'aimerais que le compilateur se comporte :

Supposons une bibliothèque avec des fonctions yes(): string! qui ne retourne jamais null et no(): string? qui peuvent retourner null.

La définition de .d.ts est :

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

Je veux pouvoir créer de grands projets dans TS qui bénéficient de valeurs nulles vérifiées statiquement et utilisent les définitions de bibliothèque existantes. De plus, je veux pouvoir passer _progressivement_ à une situation où toutes les bibliothèques sont mises à jour avec des informations de nullité correctes.

En utilisant le modificateur non nul proposé ! ci-dessus, je suis capable de le faire :

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.

Comment cela fonctionnerait-il avec votre idée ?

Ce fil est une discussion trop longue (130 commentaires !), ce qui rend très difficile le suivi des idées, suggestions et problèmes évoqués par tout le monde.

Je suggère que nous créions des éléments externes avec nos propositions, y compris la syntaxe suggérée, ce qui est accepté par TS, ce qui est une erreur, etc.

@spion puisque votre idée implique des drapeaux de compilateur, pour chaque morceau de code, vous devez documenter le comportement de TS avec le drapeau défini et non défini.

Voici l'essentiel pour le marqueur non nul !T :
https://gist.github.com/jods4/cb31547f972f8c6bbc8b

C'est la même chose que dans mon commentaire ci-dessus, avec les différences suivantes :

  • J'ai remarqué que l'aspect « non nul » des paramètres de fonction peut être déduit en toute sécurité par le compilateur (merci à @spion pour cette information).
  • J'ai inclus des commentaires @Griffork , notamment sur la déduction des valeurs de retour et j'ai essayé !T au lieu de T! .
  • J'ai ajouté quelques sections, par exemple sur le comportement des constructeurs.

N'hésitez pas à commenter, bifurquer et créer des propositions alternatives ( ?T ).
Essayons de faire avancer les choses.

@jods4

Dans les deux cas la 2ème surcharge sera utilisée

Ce qui, pour moi, implique que le correctif n'a aucun effet ?

  1. Cela implique que la résolution de surcharge est modifiée pour rendre la sémantique du langage cohérente pour les deux indicateurs.

Oui il compile sans erreur dans les deux cas, mais pas au même résultat. sumWordCounts() sera tapé sous forme de nombre ! dans un cas et un numéro ? dans la seconde. Changer la sémantique du code avec un commutateur n'est pas du tout souhaitable. Comme démontré précédemment, ce changement pourrait alors avoir des effets globaux, par exemple sur la résolution des surcharges.

  1. Il n'y a pas de number! dans ma proposition. Le type sera simplement number dans les deux cas. Ce qui signifie que la résolution de surcharge continue de fonctionner normalement, sauf avec des valeurs nulles, auquel cas le nouveau comportement des valeurs nulles explicites prenant le pas sur les valeurs implicites normalise la sémantique de manière rétrocompatible.

Non! C'est tout l'intérêt : je veux que le compilateur me renvoie une erreur lorsque j'utilise une valeur potentiellement nulle. S'il "suppose" non nul comme je le fais quand je code, alors cette fonctionnalité est inutile !

  1. Le point que j'essayais d'exprimer est qu'il y a très peu d'incompatibilité descendante de la fonctionnalité (en termes de déclarations de type). Si vous ne l'utilisez pas, vous obtenez exactement le même comportement qu'avant. Si vous souhaitez l'utiliser pour exprimer la possibilité qu'une valeur de type null puisse être renvoyée, vous le pouvez maintenant.

C'est comme si le compilateur utilisait le type string pour les valeurs de type object et string auparavant, autorisant toutes les méthodes de chaîne sur tous les objets et ne les vérifiant jamais. Maintenant, il aurait un type distinct pour Object , et vous pouvez commencer à utiliser ce type à la place pour indiquer que les méthodes de chaîne ne sont pas toujours disponibles.

Je ne suis pas sûr de comprendre, car "fonction comme ci-dessus" accepte en fait les valeurs nulles ...

Regardons cette fonction :

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

Sous le nouveau drapeau du compilateur, vous ne pourriez pas l'appeler avec des valeurs nulles, par exemple sumWordCounts(null, null); et c'est la seule différence. La fonction elle-même sera compilée car la définition de wordCounts dit qu'elle prend une chaîne et renvoie un nombre.

En lisant la fin de votre dernier commentaire, j'ai l'impression que nous n'obtiendrons pas de véritable analyse statique nulle tant que nous n'allumerons pas le commutateur, ce que vous ne pourrez pas faire tant que toutes vos définitions de bibliothèque ne seront pas corrigées. :(

Une grande majorité de code ne traite tout simplement pas vraiment les valeurs nulles ou indéfinies, à part vérifier si elles sont nulles/non définies et lancer une erreur pour empêcher la propagation de cette valeur dans la base de code à un endroit complètement indépendant, ce qui rend la tâche difficile. à déboguer. Il y a quelques fonctions ici et là utilisant des arguments facultatifs, qui sont reconnaissables et peuvent probablement être modélisés en conséquence avec le nouveau commutateur. Ce n'est pas si souvent que les fonctions renvoient des valeurs nulles comme mon exemple (que je l'ai utilisé uniquement pour faire un point sur la fonctionnalité, pas comme un cas représentatif)

Ce que je dis, c'est que la grande majorité des définitions de type resteront correctes, et pour les quelques cas restants où nous devons faire le correctif, nous pourrions choisir de ne pas les corriger si nous jugeons le cas nul sans importance, ou pour changez le type en conséquence, si nous nous en soucions. Ce qui est tout à fait conforme à l'objectif de tapuscrit de progressivement « monter le cadran » pour obtenir des garanties plus solides chaque fois que nous en avons besoin.


D'accord, donc les définitions de type existantes sont

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

Et disons que votre code a été écrit avant l'ajout de la fonctionnalité :

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

Sous le nouveau compilateur, le comportement sera exactement le même que l'ancien

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

À moins que vous n'essayiez quelque chose dont le compilateur sait qu'il peut être nul, comme le résultat de x()

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

Vous regardez no() et vous pouvez soit décider que les cas où il renvoie null sont rares donc vous n'allez pas modéliser cela, ou vous pouvez corriger la définition de type.

Voyons maintenant ce qui se passe avec votre proposition : chaque ligne de code qui touche même une bibliothèque externe se brise. Les fonctions qui avaient des définitions de type parfaitement valides comme ci-dessus se cassent également. Vous devez mettre à jour chaque annotation d'argument partout et ajouter le ! pour que le compilateur arrête de se plaindre, ou ajouter des vérifications nulles partout.

L'essentiel est que le système de type dans TypeScript est actuellement erroné. Il a une double interprétation des valeurs nulles :

Lorsque nous essayons d'affecter null à une variable de type T , ou de passer null comme argument où un type T est requis, il agit comme si le type était T|null .

Lorsque nous essayons d'utiliser une valeur de type T , cela agit comme si le type était T et il n'y a aucune possibilité de null.

Votre proposition suggère de traiter tous les types normaux T comme T|null toujours ; le mien suggère de les traiter comme juste T . Les deux changent la sémantique. Les deux font que le compilateur se comporte "correctement"

Mon argument est que la variante "juste T " est beaucoup moins douloureuse qu'il n'y paraît et en phase avec la grande majorité du code.

edit : je viens de me rendre compte que votre proposition pourrait être de continuer à traiter les types sans "!" ou "?" de la même manière qu'avant. C'est rétrocompatible, oui, mais beaucoup de travail pour obtenir des avantages (car la grande majorité du code ne traite tout simplement pas les valeurs nulles autres que la vérification/le lancement.

@spion

1. Cela implique que la résolution de surcharge est modifiée pour rendre la sémantique du langage cohérente pour les deux indicateurs.

Vous ne faites qu'énoncer le même fait, mais je ne sais toujours pas à quel cas il s'adresse et de quelle manière.
C'est pourquoi j'ai demandé un exemple concret.

1.Il n'y a pas de number! dans ma proposition. Le type sera simplement number dans les deux cas.

C'est incorrect, je sais que la syntaxe est différente mais les concepts sous-jacents sont les mêmes. Quand je dis number! , j'insiste sur un type de nombre non nul, qui dans votre proposition serait simplement number . Et le deuxième cas ne serait pas number dans votre cas, mais number | null .

Une grande majorité de code ne traite tout simplement pas vraiment les valeurs nulles ou indéfinies, à part vérifier si elles sont nulles/non définies et lancer une erreur pour empêcher la propagation de cette valeur dans la base de code à un endroit complètement indépendant, ce qui rend la tâche difficile. à déboguer. Il y a quelques fonctions ici et là utilisant des arguments facultatifs, qui sont reconnaissables et peuvent probablement être modélisés en conséquence avec le nouveau commutateur. Ce n'est pas si souvent que les fonctions renvoient des valeurs nulles comme mon exemple (que je l'ai utilisé uniquement pour faire un point sur la fonctionnalité, pas comme un cas représentatif)

Je pense que des descriptions comme celle-ci font _beaucoup_ d'hypothèses et de raccourcis. C'est pourquoi je pense que pour progresser, nous devons passer à des exemples plus concrets avec du code, de la syntaxe et des explications sur le fonctionnement du système. Vous dites:

Une grande majorité du code ne traite tout simplement pas vraiment des valeurs nulles ou indéfinies, à part vérifier si elles sont nulles/non définies et générer une erreur

Très discutable.

Il y a quelques fonctions ici et là utilisant des arguments optionnels.

Encore plus discutable. Les bibliothèques JS regorgent de tels exemples.

, qui sont reconnaissables et peuvent probablement être modélisés en conséquence avec le nouveau commutateur

Faites une proposition plus concrète car c'est un énorme raccourci. Je pense que je peux voir où cela se dirige pour le code JS réel, mais comment "reconnaîtriez-vous" un argument facultatif dans un declare function(x: {}); ou dans un interface ?

Ce n'est pas si souvent que les fonctions renvoient des valeurs nulles comme mon exemple.

Encore une fois très discutable. De nombreuses fonctions renvoient une null ou undefined : find (lorsque l'élément n'est pas trouvé), getError (lorsqu'il n'y a pas d'erreur) et ainsi de suite ... Si vous voulez plus d'exemples, regardez simplement les API de navigateur standard, vous trouverez _plenty_.

Ce que je dis, c'est que la grande majorité des définitions de types resteront correctes.

Comme vous pouvez le constater d'après mes commentaires précédents, je n'en suis pas convaincu.

et pour les quelques cas restants où nous devons effectuer le correctif, nous pouvons choisir de ne pas les corriger si nous considérons que le cas nul n'est pas important, ou de changer le type en conséquence, si nous nous en soucions. Ce qui est tout à fait conforme à l'objectif de tapuscrit de progressivement « monter le cadran » pour obtenir des garanties plus solides chaque fois que nous en avons besoin.

À ce stade, cette déclaration ne me semble pas triviale. Pouvez-vous donner des exemples concrets de la façon dont cela fonctionne? Surtout la partie _progressivement_.

Voyons maintenant ce qui se passe avec votre proposition : chaque ligne de code qui touche même une bibliothèque externe se brise. Les fonctions qui avaient des définitions de type parfaitement valides comme ci-dessus se cassent également. Vous devez mettre à jour chaque annotation d'argument partout et ajouter le ! pour que le compilateur arrête de se plaindre, ou ajouter des vérifications nulles partout.

C'est presque complètement faux. Veuillez lire attentivement l'essentiel que j'ai écrit. Vous remarquerez qu'en fait, peu d'annotations non nulles sont requises. Mais il y a un changement de rupture, oui.

Supposons que vous preniez une base de code existante qui utilise des définitions de bibliothèque et que vous essayiez de la compiler avec ma proposition sans modifier le code :

  • Vous obtiendrez de nombreux avantages pour l'analyse nulle, même sans annotations (à peu près de la même manière que vous obtenez cela dans le compilateur Flow).
  • Une seule chose va casser : utiliser la valeur de retour d'une fonction de bibliothèque dont vous savez qu'elle ne renvoie pas null.
declare function f(): string; // existing declaration, but f will never return null.
var x = f();
x.toUpperCase();  // error, possibly null reference.

Le correctif à long terme consiste à mettre à jour les définitions de la bibliothèque pour qu'elles soient plus précises : declare function f(): !string;
La solution à court terme consiste à ajouter un cast non nul : var x = <!>f(); .
Et pour aider les grands projets avec de nombreuses dépendances à se mettre à niveau plus facilement, je suggère d'ajouter un indicateur de compilateur similaire à "aucun implicite": "traiter les références nulles possibles comme un avertissement". Cela signifie que vous pouvez utiliser le nouveau compilateur et attendre que les bibliothèques aient mis à jour la définition. Remarque : contrairement à votre proposition, ce drapeau ne modifie pas la sémantique du compilateur. Cela ne change que ce qui est signalé comme une erreur.

Comme je l'ai dit, je ne suis pas sûr que nous fassions des progrès dans ce débat. Je vous suggère de regarder mon idée et d'en créer un avec vos propres idées, exemples de code et comportement sous les deux états de votre drapeau. Comme nous sommes tous des codeurs, je pense que ce sera plus clair. Il y a aussi beaucoup de cas limites à considérer. Cela fait, j'ai des tonnes de questions à poser pour des situations particulières mais ça ne sert vraiment à rien de discuter de concepts et d'idées sans une définition précise.

Il y a trop de discussions sur différentes choses qui se passent dans ce numéro de plus de 130 commentaires.

Je suggère que nous continuions la discussion _général_ ici.

Pour les discussions sur des propositions concrètes qui peuvent implémenter des types non nuls dans TS, je suggère que nous ouvrions de nouveaux problèmes, chacun concernant une seule proposition de conception. J'ai créé #1800 pour discuter de la syntaxe !T .

@spion Je vous suggère également de créer un problème pour votre conception.

Beaucoup de gens veulent des types non nuls. C'est facile dans les nouvelles langues (par exemple Rust) mais c'est très difficile à adapter dans les langues existantes (les gens demandent depuis longtemps des références non nulles en .net - et je ne pense pas que nous aurons jamais eux). En regardant les commentaires ici, vous montrez que ce n'est pas un problème trivial.
Flow m'a convaincu que cela peut être fait pour TS, essayons d'y arriver !

@jods4 Je suis désolé de le dire mais votre proposition n'a rien à voir avec un type non nul, elle est plus liée à une sorte d'analyse de flux de contrôle, difficilement prévisible, et qui brise complètement la rétrocompatibilité (en fait la plupart du code qui est valide 1,4 ts échouera selon la règle décrite dans votre résumé).
Je suis d'accord avec le fait que la discussion ne mène nulle part, et que 130 commentaires+ c'est peut-être trop. Mais c'est peut-être parce qu'il y a 2 groupes de personnes qui discutent ici :

  • ceux qui pensent que le type non nul devrait être par défaut et veulent trouver un moyen pour que cela se produise (via un indicateur ou tout autre mécanisme)
  • ceux qui ne veulent pas de type non nul et essaient juste d'éviter leur introduction, ou de les pousser dans une nouvelle syntaxe.

Ces 2 groupes de personnes ont cessé depuis longtemps de s'écouter les uns les autres, et au final ce dont nous avons besoin c'est du point de vue de l'équipe TS, d'ici là je vais personnellement arrêter d'essayer de discuter davantage sur ce sujet.

@fdecampredon
En ce qui concerne la partie concernant mon point essentiel, j'ai copié-collé vos arguments dans #1800 et si vous êtes vraiment intéressé, nous pouvons discuter de la raison pour laquelle c'est un mauvais design. Je ne comprends pas vraiment votre point de vue mais je ne veux pas commencer à discuter ici, c'est pourquoi j'ai créé le #1800 en premier lieu.

Concernant ce fil, je suis d'accord avec toi pour dire que la discussion ne mène nulle part...

Comme vous pouvez le constater d'après mes commentaires précédents, je n'en suis pas convaincu.

Eh bien, je ne sais vraiment pas comment l'expliquer d'une autre manière - je l'ai déjà expliqué plusieurs fois. Laisse-moi essayer encore une fois.

En ce moment, vous ne pouvez pas exprimer cela

declare function err():string;

renvoie null. C'est impossible, car tapscript vous permettra toujours de faire err().split(' ') . Vous ne pouvez pas dire "cela n'est peut-être pas valide".

Après ce changement, vous pourrez, en changeant string en ?string ou string|null

Mais si vous ne le faites pas, _vous ne perdez rien_ de ce que vous aviez avant le changement :

Vous n'aviez pas de vérification nulle avant le changement, et vous n'en avez pas après (si vous ne faites rien). J'ai soutenu que cela est principalement compatible avec les versions antérieures. Reste à démontrer sur des bases de code plus importantes, bien sûr.

La différence est la suivante : vous ne pouviez pas forcer la vérification des valeurs nulles avant le changement, mais vous le pouvez après (progressivement, d'abord sur les définitions qui vous intéressent le plus, puis à d'autres endroits)

@fdecampredon Je discute toujours du sujet car je pense qu'il y a beaucoup d'idées fausses sur le problème et sur la façon dont il sera "difficile" et "strict" de profiter de la fonctionnalité. Je soupçonne que beaucoup de problèmes potentiels sont gravement exagérés. Nous devrions probablement essayer d'implémenter une variante de base de --noImplicitNull dans un fork - je ne suis pas sûr de pouvoir trouver le temps dans cette prochaine période, mais si je le fais, je vais essayer , Le project semble amusant.

@spion , je suis tout à fait pour que les nulls soient déclarés avec le mot-clé null, mais quel est le comportement entre cela et undefined dans votre proposition ?

Et @fdecampredon, ma seule demande était de ne pas avoir de changement majeur, en particulier celui dans lequel les erreurs étaient affichées au mauvais endroit.

Je suppose que @spion dans votre exemple actuel, vous pouvez à peu près continuer à coder Typescript de la même manière que nous le faisons maintenant, si vous supposez qu'une variable locale si elle n'est pas affectée est convertie en paramètres de fonction nullable et avec un ? peut également être nul. Eh bien, à l'exception de cet exemple que vous avez eu plus tôt avec les premiers paramètres de fonction nullables.

@spion
Ce que je comprends de votre dernier commentaire est très différent de ce que j'avais compris avant...

Donc : string est "votre ancien type de chaîne qui ne sera jamais vérifié pour la nullité, comme <any> n'est jamais vérifié pour l'exactitude du type".

La nouvelle syntaxe : string | null est rétrocompatible car elle n'existait pas auparavant et accéder à ce type sans vérification nulle est une erreur.

C'est intéressant et il est difficile d'en saisir toutes les implications. Je vais devoir y penser pendant un certain temps.

La première question qui vient à l'esprit est de savoir comment imposer des contraintes sur les valeurs d'entrée (par exemple les paramètres de fonction) :

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

Est-ce ok ou une erreur ? Si tout va bien, comment pourrais-je en faire une erreur.

_Je pense toujours que toute idée à l'intérieur de cette discussion est perdue, ne voulez-vous pas ouvrir un nouveau problème avec votre idée ? On pourrait en discuter là-bas._

@jods ce serait une erreur :

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 Je ne pense pas que nous ayons besoin de créer plusieurs problèmes sur un seul sujet, alors les choses deviendront juste plus difficiles à suivre car vous devrez passer à 10 propositions différentes juste pour voir si quelque chose s'est passé / vous abonner à 10 différentes . Et ils devraient tous avoir des liens vers les autres propositions dans leurs introductions afin que tous ceux qui recherchent la non-nullabilité ne voient pas et ne votent pas seulement pour une.

@fdecampredon Je sais qu'il y a beaucoup de messages et beaucoup de choses non résolues, mais cela ne veut pas dire que nous devons arrêter d'essayer de trouver une solution que tout le monde aime. Si vous n'aimez pas le fait que nous soyons toujours heureux d'en parler et de nous expliquer, vous êtes plus que bienvenu pour vous désinscrire du sujet.

@Griffork
Mais seulement après avoir activé le drapeau magique, n'est-ce pas ?
Donc, avec le drapeau désactivé, vous avez du code non vérifié, à l'exception des nouveaux types nullables qui n'existaient pas auparavant ; puis lorsque vous activez le drapeau, tous les types sont non nullables et cochés ?

Je pense que le résultat final est probablement la meilleure solution. _Mais il casse à peu près tout le code qui existe aujourd'hui._ Imaginez que j'ai 300K lignes de code... Je dois ajouter une annotation nullable partout où un type est en fait nullable avant de pouvoir activer le drapeau. C'est une grosse affaire.

Si je comprends bien, c'est bien pour les nouveaux projets (une fois les .d.ts mis à jour) mais très pénible pour le code existant.

@Griffork mon problème avec ce seul problème et son long fil de commentaires est qu'il est très difficile d'avoir une discussion ciblée sur une seule proposition. S'abonner à 3 ou 4 numéros n'est pas une grosse affaire, et vous avez un bon contexte dans chacun.

La proposition originale de @spion n'avait pas de drapeau magique. C'était un changement fondamental dans le fonctionnement du tapuscrit.

Oui, mais quelqu'un qui entre dans cette discussion devrait lire tout ce qui l'a précédé. Si vous pensez que les gens ne devraient pas lire tout ce qui a précédé (et donc potentiellement proposer les mêmes propositions) _alors_ nous pouvons le diviser ; mais je ne suis pas d'accord. Le fait d'être au même endroit est que bien que nous parlions de différents détails ou de différentes implémentations possibles, nous parlions tous de la même chose .
Le même sujet, pas des sujets différents.
Toute la conversation est centralisée, et rien n'est enterré par l'absence de réponse.

Vous ne pouvez pas non plus vraiment décomposer cette conversation par détail de mise en œuvre, car de nombreuses propositions toucheront souvent à de nombreux détails différents qui sont actuellement en cours de discussion.

Séparer les propositions les unes des autres signifie que si quelqu'un détecte un défaut, il doit se rendre sur plusieurs fils de discussion différents et l'écrire. Les gens n'apprennent alors pas des erreurs des autres, et les gens ont la capacité (ce qu'ils feront) de s'isoler dans une discussion et de répéter les erreurs décrites dans d'autres discussions.

Le fait est que : diviser la conversation obligera les gens à répéter beaucoup de messages à cause des téléspectateurs qui sont trop paresseux pour tout lire dans tous les autres messages liés (en supposant que tous les messages pertinents relient tous les autres messages ).

De plus, @ jods4, ce n'est qu'un changement

Imo est beaucoup moins un changement de rupture que votre proposition, qui aurait chaque définition qui ne devrait pas avoir de null lui être assignable doit être vérifiée/touchée avant que vous puissiez les utiliser et obtenir les avantages des types non nullables.

Voici un exemple de ce à quoi pourrait ressembler le processus de "mise à niveau" des propositions de @jods4 et @spion (en supposant que les indicateurs nécessaires soient activés) :

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.

En fin de compte, ils sont tous les deux des changements décisifs. Ils sont tous les deux tout aussi pénibles à déboguer, des erreurs se produisant lors de l'utilisation de la variable au lieu de la déclaration de la variable. La différence est qu'une erreur lorsque null est affecté à quelque chose (@spion), et les autres erreurs lorsque null n'est pas affecté à quelque chose (@jods4). Personnellement, j'affecte null aux choses beaucoup moins souvent que lorsque je n'affecte pas null aux choses, donc @spion 's est moins un changement pour moi avec

D'autres choses que j'aime :

  • Les types de variables ne changent pas sur moi de manière dynamique. Si je voulais ça, j'utiliserais flow :
var s: string;
s.toUpperCase(); // error
var t = (s || "test").toUpperCase(); // ok. Inferred t: string
yes(t);  // ok
t = no();
yes(t);  // error

Personnellement, je voudrais une erreur si j'appelais no(), pas un changement de type implicite.

  • Nullables contenant le mot null, plutôt qu'un symbole, il est plus facile à lire et moins à retenir. Vraiment ! doit être utilisé pour "not" et non "not-null". Je déteste devoir me rappeler à quoi sert un symbole, tout comme je déteste les acronymes, je ne m'en souviens jamais et ils ralentissent considérablement ma vitesse de travail.

@ jods4 Je sais que la conversation actuelle est difficile à rattraper pour les gens, mais la diviser en problèmes distincts ne va pas aider, car nous devrons éventuellement soulever tous ces points dans chacun d'eux parce que le mal informés poseront les mêmes questions que nous. Cela ne va vraiment pas _aider_ la conversation, cela la rendra juste plus difficile à maintenir et à suivre.

@Griffork honnêtement, j'ai tout lu, mais je doute que beaucoup de gens le

C'est un changement décisif à chaque fois que vous avez quelque chose qui est nullable (ce qui implique qu'il est assigné null à un moment donné, sinon il ne serait pas vraiment nullable). L'erreur sera signalée lorsque null est attribué / transmis à une fonction, mais le correctif est à la déclaration. Cela inclut les déclarations de fonctions, les déclarations de variables, les déclarations de champs dans les classes... Beaucoup de choses doivent potentiellement être revues et modifiées.

@ jods4, les sous-discussions non traitées auraient dû être dérivées, et non les informations de base réelles. Mais il est trop tard pour revenir en arrière et changer cela maintenant.

@spion Je pense avoir une solution plus simple au problème de @RyanCavanaugh avec le drapeau '-nonImplicitNull', l'idée est assez simple, nous devons simplement ne pas autoriser ?type type|null ou type|undefined sans ce drapeau.

@fdecampredon alors vous avez besoin de deux versions de chaque bibliothèque disponible et tenue à jour.
Cela tuerait également notre processus où nous avons un projet non strict pour essayer du code et exécuter des tests qui dépendent du code d'un projet plus strict.

@jods4

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

Lorsque vous utilisez le drapeau --noImplicitNull proposé, oui, ce serait une erreur.

Je ne suis pas encore à l'aise pour ouvrir une proposition officielle, je veux au moins faire quelques expériences de réflexion supplémentaires ou peut-être essayer de mettre en œuvre une version plus simple de l'idée dans un fork.

J'ai remarqué que la proposition !T cassait également beaucoup de code. Non pas parce que la sémantique des types existants a changé, mais parce que la nouvelle analyse soulèvera des erreurs à de nombreux endroits.

Alors je l'ai fermé. Si nous cassons beaucoup de code, je préfère l'autre approche, cela semble plus propre.

Je suis maintenant convaincu qu'il n'y a aucun moyen d'introduire cette fonctionnalité sans casser beaucoup de code existant. C'est toujours bon pour tout le code qui n'a pas encore été écrit et peut-être que l'ancien code peut continuer à être compilé sans les avantages des vérifications nulles par le compilateur -- ce qui est exactement ce qu'il en est aujourd'hui.

Je me demande quelle est la position de l'équipe officielle de TS concernant l'aspect de rupture de cela par rapport à ses avantages.

Il est très peu probable que nous fassions jamais une pause aussi importante que « rendre tout non nullable par défaut ». S'il y avait une proposition qui produirait _uniquement_ de nouvelles erreurs dans des cas qui sont manifestement des bogues, cela pourrait être sur la table, mais des propositions comme celle-ci sont difficiles à trouver :wink:

Si la portée de l'impact était plus petite - changements dans la résolution de la surcharge lors du passage de null comme argument, par exemple, où le comportement exact n'est pas nécessairement immédiatement évident, cela pourrait être une autre histoire.

:+1 : personne sensée détectée :

Que diriez-vous de ne pas modifier la façon dont les choses fonctionnent, mais de l'introduction d'un type nul pour les unions, qui vous oblige à protéger la variable avant de l'utiliser (ou de l'affecter à une variable de type non nul) ? Le comportement par défaut permet toujours d'affecter des valeurs NULL aux variables typées normalement ainsi qu'aux variables typées avec NULL, cependant une fonction qui peut retourner NULL si elle est correctement balisée forcera un transtypage.

De cette façon, il n'y a pas de changements de rupture, mais avec de bonnes pratiques, vous pouvez toujours avoir un tas de bogues détectés par le système de frappe avec à la fois une frappe déduite et de bonnes pratiques de codage.

Ensuite (plus tard peut-être ?) vous pouvez ajouter un indicateur --noImplicitNullCast qui empêche l'affectation de null aux variables qui n'ont pas null dans leur typage (cela fonctionne plus ou moins comme la suggestion de @spion avec l'indicateur activé).

J'imagine que cela ne serait pas trop différent de toute frappe normale et de l'ajout de l'indicateur --noImplicitAny.

@Griffork
La 1ère moitié du package (ajouter un type null et interdire le déréférencement sans garde) n'est pas compatible à 100%. Vous allez casser plus de code que vous ne le pensez. Considère ceci:

Avec cette nouvelle capacité, de nombreuses définitions de bibliothèque seront mises à jour, y compris celle intégrée. Disons qu'ils modifient certaines signatures pour inclure explicitement null comme valeur de retour possible. Quelques exemples:

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

Il existe de nombreuses API de ce type : find renvoie undefined si aucun élément du tableau ne correspond au prédicat, pop renvoie undefined si le tableau est vide, localStorage.getItem ou sessionStorage.getItem renvoient tous les deux null si la clé n'a pas été trouvée.

Le code suivant est légitime et parfaitement compilé avant. Il va maintenant rompre avec une erreur :

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)

Même idée si vous récupérez quelque chose de localStorage que vous savez être là. Il y a beaucoup de code comme ça et il faut maintenant un cast (ou une nouvelle syntaxe) pour compiler et dire au compilateur : supposez que ce n'est pas nul, je sais ce que je fais.

Cela inclura bien sûr quelques bugs réels. Mais cela inclura également de nombreux faux positifs et ce sont des changements de rupture. Dans 100K+ LOC, ce n'est pas une mise à niveau triviale du compilateur.

Cela signifie probablement que la seule approche qui fonctionnerait est la suivante : le nouveau code écrit à partir de zéro ou l'ancien code migré tire pleinement parti de la vérification des valeurs nulles ; le code hérité n'a aucun avantage. Ceci étant conduit par une option de compilateur (--enableNullAnalysis) et sans chemin de migration « de transition » ou « progressive ».

Hé bien oui. Mais vous pouvez toujours geler les lib.d.ts d'un projet ou en récupérer un ancien.
Une nouvelle frappe cassera l'ancien code, cela arrivera de toute façon. Presque tous les nouveaux types, lorsqu'ils sont introduits, nécessitent que l'ancien code soit mis à jour d'une manière ou d'une autre pour fonctionner correctement (par exemple, les syndicats, les génériques).
Ce changement signifie que ceux qui souhaitent ignorer la mise à jour ou geler leurs définitions de bibliothèque/code n'en souffriront pas.
Théoriquement, la plupart du code utilisant ces fonctions doit être protégé de toute façon (et se trouve généralement dans de grandes bases de code) - car il y a des bogues cachés dans votre code si vous ne le faites pas. Ainsi, ce changement détecterait plus probablement les erreurs que de provoquer des changements de rupture généralisés.

Éditer
@ jods4 pourquoi avez-vous utilisé un code dangereux comme exemple alors que c'est exactement le genre de code qui pourrait provoquer une erreur que nous essayons de détecter en effectuant cette modification ?

Modifier 2
Si vous ne cassez pas le code _some_ (mauvaise / risque), alors il est inutile de faire de changement par rapport à ce sujet jamais.

Mais encore une fois, puisqu'il ne s'agit que d'un problème de syntaxe, tout se compilerait toujours correctement, ce serait juste une erreur partout.

_Une nouvelle frappe cassera l'ancien code, cela arrivera de toute façon._

Ce n'est pas le cas. Sauf circonstances exceptionnelles, nous n'ajouterions pas de nouvelles fonctionnalités qui cassent le code existant. Les génériques et les unions n'ont pas été ajoutés à lib.d.ts de manière à obliger les utilisateurs de lib.d.ts à mettre à jour leur base de code afin d'utiliser la nouvelle version du compilateur. Oui, les gens peuvent choisir d'utiliser une ancienne version de la bibliothèque, mais ce n'est tout simplement pas le cas que nous allons prendre des changements de langue qui cassent beaucoup de code existant (comme c'est le cas pour pratiquement toutes les langues principales). Il y aura des exceptions occasionnelles à cela (https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes) mais elles seront rares, le plus souvent si nous pensons que le code que nous cassons ne peut être que un bug.

Mais vous pouvez toujours geler les lib.d.ts d'un projet ou en récupérer un ancien

Le gel de lib.d.ts (et de tout autre .d.ts que je peux utiliser BTW) a de très fortes implications. Cela signifie que je ne peux pas obtenir de mises à jour sur les bibliothèques que j'utilise ou sur les nouvelles API HTML. Ce n'est pas quelque chose à prendre à la légère.

Théoriquement, la plupart du code utilisant ces fonctions doit être protégé de toute façon (et se trouve généralement dans de grandes bases de code) - car il y a des bogues cachés dans votre code si vous ne le faites pas.

JS a pour tradition de renvoyer undefined (ou parfois null ) dans de nombreux cas, ce qui générerait une erreur dans d'autres langues. Un exemple de ceci est Array.prototype.pop . En C#, sauter d'une pile vide serait jeté. On peut donc dire qu'il renvoie toujours une valeur valide. En Javascript, un tableau vide renvoie undefined . Si votre système de types est strict à ce sujet, vous devez vous en occuper d'une manière ou d'une autre.

Je savais que vous alliez répondre à cette question, c'est pourquoi j'ai écrit des exemples de code légitime et fonctionnel. Dans les grandes bases de code, vous trouverez de nombreux exemples où une API peut retourner null dans certaines situations, mais vous savez que c'est sûr dans votre cas spécifique (et donc vous ignorez les contrôles de sécurité).

Je regardais la partie micro-tâche d'une bibliothèque il y a à peine une heure. La partie principale se résume essentiellement à ceci:

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

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

Il y a beaucoup de cas comme celui-ci.

Concernant votre Edit 2 : oui, j'espère que ce changement _attrapera_ les bogues et signalera le code invalide. Je suis convaincu que c'est utile et c'est pourquoi j'aimerais que cela se produise d'une manière ou d'une autre. Mais l'équipe TS prendra en compte les changements de rupture dans le code _valid_. Il y a des compromis qui doivent être décidés ici.

@danquirk Fair, alors je suppose que cette fonctionnalité ne pourra jamais être implémentée.
@ jods4 Je comprends ce que vous dites, mais les nullables ne peuvent jamais être implémentés sans casser ce code.

Triste, dois-je enfin fermer ce sujet ?

Probablement.

Je pense que si nous évitons l'argument nullable et non nullable ...

Le problème peut être résolu (ou atténué) de manière non intrusive grâce à un ensemble de nouvelles fonctionnalités :

  • Présentez le symbole null-typeguard ?<type>
    Si un type est annoté avec ce symbole alors c'est une erreur d'y accéder directement

``` TypeScript
var foo: ?string;

foo.indexOf('s'); // Erreur
foo && foo.indexOf('s'); // D'accord
```

  • Présentez un drapeau --nouseofunassignedlocalvar

``` TypeScript
var foo : chaîne ;

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

foo = 'bar';

foo.indexOf('s'); // d'accord
```

Note historique intéressante : en essayant de trouver un problème connexe à cela, je suis tombé sur un ancien problème sur codeplex qui mentionnait une option de compilateur --cflowu en février 2013. @RyanCavanaugh , je me demande ce qui est arrivé à ce drapeau ?

  • Présentez l'opérateur de navigation sécurisée #16

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

Combinées, ces fonctionnalités aideraient à piéger davantage d'erreurs autour de l'utilisation de null sans provoquer réellement une dépression nerveuse chez tout le monde.

@NoelAbrahams :
Votre première proposition est essentiellement exactement la même que ma dernière, vous utilisez juste un ? au lieu de |null (lisez le message de @jods4 sur les problèmes de mise à jour de lib.d.ts et les modifications de rupture).

Votre deuxième proposition a un problème précédemment résolu par @RyanCavanaugh (ci-dessus) :

Les drapeaux qui modifient la sémantique d'une langue sont une chose dangereuse. Un problème est que les effets sont potentiellement très non locaux :
...[couper]...
La seule chose sûre à faire est de garder la même sémantique d'assignabilité et de changer ce qui est une erreur par rapport à ce qui ne dépend pas d'un indicateur, un peu comme la façon dont noImplicitAny fonctionne aujourd'hui.

Je suis à peu près certain qu'un drapeau similaire a été proposé plus tôt, puis abandonné à cause du @RyanCavanaugh .

Votre troisième proposition a très peu à voir avec le sujet actuel (il ne s'agit plus d'erreurs de frappe et de compilation, mais de détecter les erreurs d'exécution). La raison pour laquelle je dis cela est parce que la raison pour laquelle ce sujet a été créé était d'aider à réduire le besoin d'effectuer des vérifications indéfinies ou nulles sur des variables connues pour être "sûres" (avec un moyen facile de suivre cela), sans ajouter de nouvelles valeurs nulles ou des contrôles indéfinis partout.

Serait-il possible d'implémenter l'une des propositions suggérées et de ne pas mettre à jour lib.d.ts pour utiliser les nullables (et les autres libs) ? Ensuite, la communauté pourrait maintenir/utiliser ses propres versions avec nullables si elle le souhaite ?

Éditer:
Plus précisément, toutes les bibliothèques contiennent les dernières informations, mais leur typage n'est pas mis à jour pour exiger des gardes de type.

@RyanCavanaugh Je reviendrai et en discuterai si/quand j'ai un correctif qui démontre que ce n'est pas du tout un changement si important (surtout si les fichiers .d.ts ne sont pas mis à jour).

Les définitions de type

Quoi qu'il en soit, il semble que ce problème soit une cause perdue.

Soit dit en passant, il y avait un compilateur modifié par MSR qui faisait cela entre autres - y a-t-il des documents disponibles avec leurs conclusions ? Edit : trouvé : http://research.microsoft.com/apps/pubs/?id=224900 mais malheureusement, je ne suis pas sûr que ce soit lié.

@Griffork , oui, je suis sûr que presque tout a été discuté sous une forme ou une autre - étant donné la longueur de la discussion. Mon résumé est pratique pour atténuer les problèmes autour de null en introduisant un certain nombre de fonctionnalités connexes.

ne pas ajouter de nouvelles vérifications nulles ou non définies partout

Le piégeage des variables locales non affectées atténue cela et l'opérateur de navigation sûre est une fonctionnalité ES proposée, il atterrira donc dans TS à un moment donné.

Je pense aussi que la probabilité que cela entre dans TS est faible... :(

La seule solution à laquelle je puisse penser est de faire de la sécurité nulle un indicateur de compilateur opt-in. Par exemple, introduisez la nouvelle syntaxe T | null ou ?T mais ne déclenchez _aucune_ erreur à moins que le projet ne l'accepte. Ainsi, les nouveaux projets bénéficient des avantages, tout comme les anciens projets qui choisissent de rendre leur code compatible avec la nouvelle fonctionnalité. Les bases de code volumineuses qui sont trop volumineuses pour être adaptées facilement n'obtiennent tout simplement pas cette fonctionnalité.

Cela dit, même ainsi, il reste plusieurs problèmes pour faire voler cette fonctionnalité ...

@NoelAbrahams désolé, je ne
J'utiliserai certainement cette fonctionnalité quand (/si) elle deviendra native, ça sonne vraiment bien.

@jods4 qui a le même problème avec le commentaire @RyanCavanaugh que j'ai cité ci-dessus (erreur d'affectation due à un problème ailleurs lorsqu'un indicateur est modifié).

Encore une fois, le moyen le plus simple consiste à implémenter l'une des autres propositions (probablement @spion 's) et à ne pas ajouter les nouveaux types au .d.ts'.

@Griffork
Pas nécessairement, bien que ce soit l'un des "plusieurs problèmes" qui restent. La fonctionnalité doit être conçue de manière à ce que l'activation de l'indicateur ne modifie _pas_ la sémantique du programme. Cela ne devrait que soulever des erreurs.

Par exemple, si nous optons pour !T design

Je dois également souligner que si T | null est un changement décisif, ?T ne l'est pas.

@ jods4 J'avais l'impression que la "modification de la sémantique" incluait la "modification de l' assignation ", de toute façon mes préoccupations (et peut-être @RyanCavanaugh ) concernant les effets non locaux sont toujours d'actualité.

@NoelAbrahams comment ?
T | null nécessite un typeguard, ?T nécessite un typeguard, ce sont la même chose avec des noms/symboles différents.
Oui, vous pouvez les activer tous les deux avec un indicateur.
Je ne vois pas la différence ?

@Griffork , la façon dont je le vois ?T signifie simplement "ne pas accéder sans vérifier null". On est libre d'ajouter cette annotation au nouveau code afin d'imposer le contrôle. Je pense à cela comme une sorte d'opérateur plutôt qu'une annotation de type.

L'annotation T|null casse le code existant car elle indique si les types sont nullables ou non par défaut.

@jbondc
Intéressant... Je n'ai pas encore approfondi votre proposition mais je pense que je le ferai.

À l'heure actuelle, l'immuabilité imposée par le compilateur n'est pas aussi intéressante en JS que dans d'autres langages car son modèle de thread interdit le partage de données. Mais sûrement quelque chose à garder à l'esprit.

@NoelAbrahams
Toutes les propositions d'application nulle sont des modifications de rupture. Même les ?T que vous avez décrits. J'ai montré plusieurs exemples pourquoi quelques commentaires ci-dessus.

C'est pourquoi je pense que la seule issue si nous voulons cela est d'en faire une option du compilateur, et de penser cela suffisamment bien pour que l'activation de l'option modifie les erreurs signalées mais pas le comportement du programme. Je ne sais pas si c'est faisable ou pas.

En fait, je suis plutôt d'accord avec ça. Avec TS 1.5, je pourrai en quelque sorte obtenir ce que je veux :

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

maintenant, la vérification isVoid est forcée sur les valeurs de retour pour externalUnsafeFunction ou toute autre fonction ou définition de fonction où j'ajoute |void au type de retour.

Je ne pourrai toujours pas déclarer des fonctions qui n'acceptent pas null/undefined, mais il est possible d'être suffisamment diligent pour ne pas oublier d'initialiser les variables locales et les membres de la classe.

Parce que @NoelAbrahams, nous avons déjà discuté du fait que même avec un type null, null devrait toujours être autorisé à être

Cela signifie également qu'à l'avenir, nous pourrons introduire un indicateur de compilateur qui nous permettra d'annoter où et quand une fonction peut accepter null.

Et personnellement, je déteste l'idée d'utiliser des symboles pour représenter des types alors que nous pouvons utiliser des mots à la place.

@spion c'est un bon point en fait. Si cela fonctionne actuellement dans TS, alors sans doute tous les fichiers d.ts intégrés sont déjà "mauvais" , l'ajout du type null tel que proposé par vous avec mes modifications suggérées ne changera rien.

En fait, puisque c'est déjà quelque peu réalisable, je vais proposer que nous commencions à l'utiliser cette semaine à mon patron.

Je mets en garde contre le fait que nous ne considérons en aucun cas T|void comme une syntaxe que nous aurions peur de casser à l'avenir. C'est presque un non-sens.

@RyanCavanaugh C'est à peu près aussi absurde que void === undefined (une valeur assignable).

Soupir.

Notre code est trop volumineux pour démarrer en fonction de T|void s'il va bientôt se casser et ne pas être remplacé. Et je ne serai pas en mesure de convaincre les autres programmeurs de l'utiliser si cela pouvait casser une semaine.

Ok, @spion si jamais vous créez votre patch, faites-le moi savoir et je l'exécuterai sur la base de code de mon travail. Je peux au moins donner des statistiques sur le nombre d'erreurs que cela provoque.

@RyanCavanaugh absurde, vraiment? De quelle manière ? Et que suggéreriez-vous pour exprimer des types nullables qui devraient être interdits pour tout type de consommation avant une vérification null/undefined ?

J'ai vraiment beaucoup de bibliothèques qui pourraient en bénéficier.

@Griffork, il ne sera pas possible de supprimer en toute sécurité void de l'union avant 1.5 de toute façon, pas sans gardes de type définis par l'utilisateur, donc l'utiliser ne sera possible qu'après 1.5 (si cela devient possible du tout)

Un non-sens comme dans, écririez-vous jamais ce code ?

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

Bien sûr que non. Alors, quelle est la signification d'ajouter void à une union de types possibles ? Notez cette partie de la spécification du langage qui existe depuis un certain temps dans la section décrivant The Void Type (3.2.4) :

_REMARQUE : Nous pourrions envisager d'interdire la déclaration de variables de type Void car elles ne servent à rien. Cependant, comme Void est autorisé en tant qu'argument de type pour un type ou une fonction générique, il n'est pas possible d'interdire les propriétés ou les paramètres Void._

Cette question:

_Et que suggéreriez-vous pour exprimer des types nullables qui devraient être interdits pour tout type de consommation avant une vérification null/undefined ?_

est le sujet de tout le fil. Ryan fait simplement remarquer que string|void est un non-sens selon la sémantique actuelle du langage et qu'il n'est donc pas nécessairement raisonnable de prendre une dépendance vis-à-vis de la sémantique du non-sens.

Le code que j'ai écrit ci-dessus est, selon moi, parfaitement valide TS 1.5, n'est-ce pas ?

Je vais donner un exemple qui n'est certainement pas absurde. Nous avons une fonction de bibliothèque de base de données (nodejs) que nous appelons get() similaire à Linq's Single() qui renvoie malheureusement Promise<null> place en lançant une erreur (une promesse rejetée) lorsque l'élément n'est pas trouvé. Il est utilisé dans notre base de code à des milliers d'endroits et il est peu probable qu'il soit remplacé ou qu'il disparaisse de sitôt. Je souhaite écrire une définition de type qui nous oblige, ainsi que les autres développeurs, à utiliser une protection de type avant de consommer la valeur, car nous avons eu des dizaines de bogues difficiles à tracer provenant d'une valeur nulle se déplaçant loin dans la base de code avant d'être consommée de manière incorrecte.

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

Cela me semble tout à fait raisonnable. L'ajout de void à l'union rend toutes les opérations sur val invalides, comme elles devraient l'être. Le typeguard rétrécit le type en supprimant le |void et laisse la partie T .

Peut-être que la spécification ne prend pas en compte les implications des unions avec vide ?

N'interdisez pas les variables void ! Elles fonctionnent comme des valeurs de type unitaire et peuvent être utilisées
dans les expressions (contrairement à void en C# qui nécessite 2 ensembles de tout : un
pour les actions et une fonction). S'il vous plaît, laissez le vide, d'accord ?
Le 27 janvier 2015 à 20 h 54, "Gorgi Kosev" [email protected] a écrit :

Le code que j'ai écrit ci-dessus est, selon moi, parfaitement valide TS 1.5, n'est-ce pas ?

Je vais vous donner un exemple qui n'est certainement pas absurde. Nous avons un
fonction de bibliothèque de base de données similaire à Linq's Single() qui malheureusement
retours Promesseà la place, lancer une erreur (une promesse rejetée) lorsque
l'article n'est pas trouvé. Il est utilisé dans notre base de code dans des milliers de
endroits et n'est pas susceptible d'être remplacé ou de disparaître bientôt. je veux écrire un
définition de type qui nous oblige, ainsi que les autres développeurs, à utiliser un type guard
avant de consommer la valeur, car nous avons eu des dizaines de bugs difficiles à retracer
provenant d'une valeur nulle se déplaçant loin dans la base de code avant d'être
consommé de manière incorrecte.

interface Héritage {
avoir(...):Promesse
}
function isVoid(val: any): val is void { return val == null; } // capture également undefined

legacy.get(...).then(val => {
// val.consumeNormally() est une erreur
if (!isVoid(val)) {
val.consumeNormalement(); // D'ACCORD
}
else { gérer la casse }
});

Cela me semble tout à fait raisonnable. Pouvez-vous indiquer quelle partie est
absurdité?

Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-71767358
.

Ce que je dis, c'est que T|void n'a pratiquement aucun sens en ce moment parce que nous avons déjà la règle selon laquelle l'union d'un type et de son sous-type équivaut simplement au supertype, par exemple Cat|Animal est équivalent à Animal . Traiter void comme un substitut pour les valeurs null / undefined n'est pas cohérent car null et undefined sont déjà dans le domaine de T _ pour tous les T , ce qui implique qu'ils doivent être des sous-types de T

En d'autres termes, T|null|undefined , si vous pouviez écrire cela, serait déjà susceptible de se réduire en T . Traiter void comme une incantation pour null|undefined est faux car si c'était réellement le cas, le compilateur aurait déjà réduit T|void en T .

Je lirais personnellement ceci http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf

Les seules valeurs possibles pour le type Void sont null et undefined. Le type Void est un sous-type du type Any et un supertype des types Null et Undefined, mais sinon Void n'est pas lié à tous les autres types.

Comme; dans T|void , T n'est spécifiquement _pas_ un supertype de void car le seul supertype (selon les spécifications) est any .

Edit: l'interdire est un problème différent.

Edit2 : relisez ce que vous avez dit, et cela a du sens littéralement (de la manière dont la documentation est vide), mais cela n'a pas de sens dans ce qui est supposé être l'interprétation commune (ou correcte) de la documentation.

@RyanCavanaugh J'utiliserai la fonctionnalité de toute façon jusqu'à ce qu'elle tombe en

Et je ne peux m'empêcher de souligner l'ironie de dire que réduire T|null|undefined en T "a du sens" alors que l'existence de T|void n'en a pas. (Je sais, c'est à propos de la spécification, mais quand même...)

@jbondc

Il n'y a pas de types structurels statiques dans ECMAScript, cet argument n'est donc pas valide. Dans ECMAScript, toutes les valeurs sont de type any donc tout peut être assigné à n'importe quoi ou passé à n'importe quoi. Ce fait ne devrait rien impliquer sur le système de type statique de TypeScript, qui est là pour améliorer celui qui n'existe pas dans JS

C'est un peu triste que je doive expliquer l'ironie, mais voici :

  1. vous avez une valeur qui ne prend en charge aucune méthode ni aucune propriété, null
  2. vous créez un système de types où cette valeur peut être affectée à des variables de tout type, la plupart d'entre elles prenant en charge diverses méthodes et propriétés (par exemple, des nombres de chaînes ou des objets complexes var s:string = null )
  3. vous créez un type void qui accepte correctement cette valeur et n'autorise aucune méthode ou propriété, et par conséquent T|void n'autorise aucune méthode ni propriété.

L'affirmation est que (3) est un non-sens, tandis que (2) ne l'est pas. Voyez-vous la partie qui est ironique ici ? Sinon, je vais essayer un autre exemple :

  1. vous avez des valeurs qui ne prennent en charge que certaines méthodes et propriétés {}
  2. vous créez un système de types où cette valeur est assignable à des variables de tout type (par exemple, des nombres de chaînes ou des objets complexes var s:string = {} ) qui ont un ensemble beaucoup plus important de méthodes et de propriétés
  3. vous avez un type {} qui accepte correctement les valeurs {} et n'autorise aucune méthode ou propriété autre que quelques-unes intégrées, et T|{} n'autorise naturellement aucune méthode ou propriété autre que le ceux intégrés de {}

Maintenant, lequel est un non-sens, (2) ou (3) ?

Quelle est la seule différence entre les deux exemples ? Quelques méthodes intégrées.

Enfin, l'interface n'était qu'une description de définition de type. Ce n'est pas une interface qui est implémentée n'importe où. C'est l'interface fournie par la bibliothèque, qui ne renvoie jamais rien d'autre que des promesses, mais peut renvoyer une promesse pour null. La sécurité est donc bien réelle.

@spion c'est exactement ce que je pensais.

Voilà, je pense que ça va éclaircir un peu les choses :
Selon la spécification :


Qu'est-ce que le vide ?

Le type Void, référencé par le mot-clé void, représente l'absence d'une valeur et est utilisé comme type de retour des fonctions sans valeur de retour.

Ainsi, void n'est pas null|undefined.

Les seules valeurs possibles pour le type Void sont null et undefined

Ainsi, null et undefined sont assignables à void (comme ils sont assignables à à peu près n'importe quoi d'autre).


Null est un type.

3.2.5 Le type nul
Le type Null correspond au type primitif JavaScript du même nom et est le type du null
littéral.
Le littéral null fait référence à la seule et unique valeur du type Null. Il n'est pas possible de référencer directement le type Null lui-même.


Undefined est un type.

3.2.6 Le type indéfini
Le type Undefined correspond au type primitif JavaScript du même nom et est le type du littéral non défini.


Void n'est qu'un Supertype des _types_ Null et Undefined

. Le type Void est un sous-type du type Any et un supertype des types Null et Undefined, mais sinon Void n'est pas lié à tous les autres types.


Donc @jbondc selon la spécification du langage any ). En fait, Void est spécifiquement écrit pour avoir un comportement différent des types Null et Undefined :

Annuler:

Le type Void est un sous-type du type Any et un supertype des types Null et Undefined, mais sinon Void n'est pas lié à tous les autres types.

Nul:

Le type Null est un sous-type de tous les types, à l'exception du type Undefined. Cela signifie que null est considéré comme une valeur valide pour tous les types primitifs, types d'objets, types d'union et paramètres de type, y compris même les types primitifs Number et Boolean.

Indéfini:

Le type indéfini est un sous-type de tous les types. Cela signifie qu'undefined est considéré comme une valeur valide pour tous les types primitifs, types d'objets, types d'union et paramètres de type.

Plus clair maintenant ?

Ce que nous demandons, c'est un type qui force une garde pour les valeurs NULL et un type qui force une garde pour les indéfinis. Je ne demande pas que null ou undefined ne soit pas attribuable à d'autres types (c'est trop cassant), juste la possibilité d'opter pour un balisage supplémentaire. Heck, ils n'ont même pas besoin de s'appeler Null ou Undefined (les types).

@jbondc c'est l'ironie : peu importe ce que dit le compilateur TS, les valeurs nulles et indéfinies dans JS ne prendront jamais en charge aucune méthode ou propriété (autre que le lancement d'exceptions). Par conséquent, les rendre assignables à n'importe quel type est un non-sens, à peu près autant de non-sens que de permettre à la valeur {} d'être affectée à des chaînes. Le type void dans TS ne contient aucune valeur, _but_ null ou undefined sont assignables à tout le monde, et peuvent donc être assignés à des variables de type void, il y a donc une autre bêtise ici. Et c'est l'ironie - que (via les gardes de type et les syndicats) les deux se combinent actuellement en quelque chose qui a du sens :)

Quelque chose d'aussi complexe que le compilateur TS a été écrit sans gardes, c'est quelque chose à quoi penser.

Il existe une assez grosse application écrite en PHP ou JAVA, qui ne rend pas le langage meilleur ou pire.
Les pyramides égyptiennes ont été construites sans aucune machine moderne, cela veut-il dire que tout ce que nous avons inventé au cours des 4500 dernières années est de la merde ?

@jbondc Voir mon commentaire ci-dessus avec la liste des problèmes dans le compilateur TypeScript causés par des valeurs nulles et non définies (moins le premier de la liste)

@jbondc ce n'est pas censé résoudre les problèmes du monde, c'est censé être un autre outil disponible à la disposition du développeur.
Cela aurait le même impact et la même utilité que les surcharges de fonctions, les unions et les alias de type.
C'est une autre syntaxe opt-in qui permet aux développeurs de saisir des valeurs avec plus de précision.

L'un des gros problèmes mentionnés pour rendre les types non nullables est ce qu'il faut faire avec le code TypeScript existant. Il y a deux problèmes ici: 1) Faire fonctionner le code existant avec un compilateur plus récent qui prend en charge les types non nullables et 2) Interopérer avec du code qui n'a pas été mis à jour - éviter un bourbier de style Python 2/3

Il devrait cependant être possible de fournir un outil qui effectue une réécriture automatisée du code TS existant pour le construire et pour ce cas limité, quelque chose qui fonctionne beaucoup mieux que, disons, 2to3 pour Python.

Peut-être qu'une migration progressive pourrait ressembler à ceci :

  1. Introduire la prise en charge de la syntaxe '?T' pour indiquer un type éventuellement nul. Initialement, cela n'a aucun effet sur la vérification de type, c'est juste là pour la documentation.
  2. Fournir un outil qui réécrit automatiquement le code existant pour utiliser '?T'. L'implémentation la plus stupide convertirait toutes les utilisations de 'T' en '?T'. Une implémentation plus intelligente vérifierait si le code qui utilisait le type supposait implicitement qu'il était non nul et utiliserait « T » dans ce cas. Pour le code qui essaie d'attribuer une valeur de type '?T' à un paramètre ou à une propriété attendant 'T', cela pourrait envelopper la valeur dans un espace réservé qui affirme (au moment de la compilation) que la valeur est non nulle - par exemple let foo: T = expect(possiblyNullFoo) ou let foo:T = possiblyNullFoo!
  3. Appliquer l'outil à toutes les définitions de types fournies et à celles du référentiel DefinitelyTyped
  4. Introduisez un avertissement du compilateur lorsque vous essayez d'affecter '?T' à un emplacement attendant un 'T'.
  5. Laissez les avertissements cuire dans la nature et vérifiez que le portage est gérable et voyez comment le changement est reçu
  6. Faire de l'affectation de '?T' à un emplacement attendant un 'T' une erreur, dans une nouvelle version majeure du compilateur.

Peut-être qu'il pourrait également être utile d'introduire quelque chose qui désactiverait les avertissements pour des modules spécifiques afin de faciliter l'utilisation du code auquel l'outil n'a pas encore été appliqué.

Dans le commentaire précédent, je suppose qu'il n'y aurait pas de mode pour choisir la nullité du simple « T » pour éviter de diviser la langue et que les étapes 1 à 3 seraient utiles en elles-mêmes, même si l'étape 6 ne s'avère jamais pratique.

Mes pensées aléatoires :

  1. La partie expect() peut être compliquée, il faudrait qu'elle soit bien pensée. Il n'y a pas que des affectations, mais des interfaces, des génériques ou des types de paramètres...
  2. Je _pense_ TS n'a aucun avertissement, seulement des erreurs. Si j'ai raison, je ne suis pas sûr qu'ils introduiront des avertissements uniquement pour cette fonctionnalité.
  3. Même si de nombreuses personnes mettent à jour leur code, les entreprises peuvent être très lentes à le faire, voire ne jamais vouloir le faire dans le cas de bases de code existantes volumineuses. Cela fait de cette version majeure un énorme changement de rupture, quelque chose que l'équipe TS ne veut pas.

Cela dit, je pense toujours qu'un indicateur "opt-in" pour signaler les erreurs nulles est une solution acceptable. Le problème est que le même code/définitions devrait avoir le même comportement (moins les erreurs) lorsque le drapeau est désactivé, ce à quoi je n'ai pas encore eu le temps de réfléchir.

@jods4 - Pour la partie expect() , je pense que partout où vous avez ?T le compilateur voit T | undefined afin que vous puissiez réutiliser les règles de gestion des types d'union aussi loin que possible.

Je suis d'accord, je ne sais pas à quel point un « jour du drapeau » est réaliste pour TypeScript. En plus d'un indicateur « opt-in », il peut également s'agir d'un indicateur opt-in qui deviendra éventuellement un indicateur opt-out. L'important est d'avoir une direction claire sur ce qu'est le style idiomatique.

Quelque chose d'autre pertinent à cette discussion - https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf traite des enquêtes menées par l'équipe Chrome sur l'utilisation des informations de type (spécifiées via TypeScript-style annotations) dans le compilateur pour optimiser JS. Il y a une question ouverte là-dedans sur la nullabilité - ils aimeraient des types non nullables mais ne sont pas non plus sûrs de la faisabilité.

Je vais juste sonner ici. Je suis surpris que nous n'ayons pas vraiment résolu celui-ci.

Je dis qu'il y a une valeur ajoutée ici. Celui qui n'est applicable qu'au moment de la compilation.
Au lieu d'avoir à écrire plus de code pour vérifier une valeur null, être capable de déclarer une valeur comme non nullable peut gagner du temps et du codage. Si je décris un type comme « non-nullable », alors il doit être initialisé et éventuellement affirmé comme non-null avant d'être transmis à une autre fonction qui attend non-null.

Comme je l'ai dit plus haut, peut-être que cela pourrait être simplement résolu par une implémentation de contrats de code.

Pourquoi ne pouvez-vous pas simplement arrêter d'utiliser le littéral nul et garder tous les endroits où
les nulls peuvent-ils s'infiltrer dans votre code ? De cette façon, vous serez à l'abri de null
référence une exception sans avoir à effectuer des vérifications pour null partout.
Le 20 février 2015 à 13h41, "electricessence" [email protected] a écrit :

Je vais juste sonner ici. Je suis surpris que nous n'ayons pas vraiment résolu
celui-ci.

Je dis qu'il y a une valeur ajoutée ici. Un qui n'est applicable qu'à la compilation
temps.
Au lieu d'avoir à écrire plus de code pour vérifier une valeur, être capable de
déclarer une valeur comme non nullable peut faire gagner du temps et du codage. Si je décris un
tapez comme « non-nullable », alors il doit être initialisé et éventuellement affirmé
comme non nul avant d'être transmis à une autre fonction qui attend
non nul.

Comme je l'ai dit plus haut, peut-être que cela pourrait être simplement résolu par un contrat de code
la mise en oeuvre.

Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204
.

Les vérifications nulles sont nettement plus rapides que les vérifications non définies.
Le 21/02/2015 11h54, "Aleksey Bykov" [email protected] a écrit :

Pourquoi ne pouvez-vous pas simplement arrêter d'utiliser le littéral nul et garder tous les endroits où
les nulls peuvent-ils s'infiltrer dans votre code ? De cette façon, vous serez à l'abri de null
référence une exception sans avoir à effectuer des vérifications pour null partout.
Le 20 février 2015 13:41, notifications "electricessence"@github.com
a écrit:

Je vais juste sonner ici. Je suis surpris que nous n'ayons pas vraiment résolu
celui-ci.

Je dis qu'il y a une valeur ajoutée ici. Un qui n'est applicable qu'à la compilation
temps.
Au lieu d'avoir à écrire plus de code pour vérifier une valeur, être capable de
déclarer une valeur comme non nullable peut faire gagner du temps et du codage. Si je décris un
tapez comme « non nullable », alors il doit être initialisé et éventuellement
affirmé
comme non nul avant d'être transmis à une autre fonction qui attend
non nul.

Comme je l'ai dit plus haut, peut-être que cela pourrait être simplement résolu par un contrat de code
la mise en oeuvre.

Répondez directement à cet e-mail ou consultez-le sur GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75295204>
.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346198
.

Mon point que ces vérifications sont inutiles (si le mot-clé null est interdit
et tous les autres endroits où il peut entrer sont gardés).

Pourquoi? 1. Le code sans aucune vérification s'exécute beaucoup plus rapidement que le code
avec des vérifications pour les valeurs nulles. 2. Il contient moins de si et donc plus lisible
et maintenable.
Le 20 février 2015 à 19h57, "Griffork" [email protected] a écrit :

Les vérifications nulles sont nettement plus rapides que les vérifications non définies.
Le 21/02/2015 11h54, "Aleksey Bykov" [email protected] a écrit :

Pourquoi ne pouvez-vous pas simplement arrêter d'utiliser le littéral nul et garder tous les endroits où
les nulls peuvent-ils s'infiltrer dans votre code ? De cette façon, vous serez à l'abri de null
référence une exception sans avoir à effectuer des vérifications pour null partout.
Le 20 février 2015 13:41, notifications "electricessence"@github.com
a écrit:

Je vais juste sonner ici. Je suis surpris que nous n'ayons pas vraiment résolu
celui-ci.

Je dis qu'il y a une valeur ajoutée ici. Un qui n'est applicable qu'à la compilation
temps.
Au lieu d'avoir à écrire plus de code pour vérifier une valeur null, être capable
à
déclarer une valeur comme non nullable peut faire gagner du temps et du codage. Si je
décrire un
tapez comme « non nullable », alors il doit être initialisé et éventuellement
affirmé
comme non nul avant d'être transmis à une autre fonction qui attend
non nul.

Comme je l'ai dit plus haut, peut-être que cela pourrait être simplement résolu par un contrat de code
la mise en oeuvre.

Répondez directement à cet e-mail ou consultez-le sur GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Répondez directement à cet e-mail ou consultez-le sur GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346198>
.

Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346390
.

Vous manquez le point, nous utilisons des valeurs nulles pour éviter les vérifications indéfinies.
Et puisque notre application n'a pas un état cohérent, nous le faisons, et
doivent souvent avoir des choses nulles / non définies au lieu d'un objet à
représenter cela.
Le 21/02/2015 à 12h20, "Aleksey Bykov" [email protected] a écrit :

Mon point que ces vérifications sont inutiles (si le mot-clé null est interdit
et tous les autres endroits où il peut entrer sont gardés).

Pourquoi? 1. Le code sans aucune vérification s'exécute beaucoup plus rapidement que le code
avec des vérifications pour les valeurs nulles. 2. Il contient moins de si et donc plus lisible
et maintenable.
Le 20 février 2015 à 19h57, "Griffork" [email protected] a écrit :

Les vérifications nulles sont nettement plus rapides que les vérifications non définies.
Le 21/02/2015 11:54, "Aleksey Bykov" [email protected]
a écrit:

Pourquoi ne pouvez-vous pas simplement arrêter d'utiliser le littéral nul et garder tous les endroits

les nulls peuvent-ils s'infiltrer dans votre code ? De cette façon, vous serez à l'abri de null
référence une exception sans avoir à effectuer des vérifications pour null partout.
Le 20 février 2015 13:41, notifications "electricessence"@github.com
a écrit:

Je vais juste sonner ici. Je suis surpris que nous n'ayons pas vraiment
résolu
celui-ci.

Je dis qu'il y a une valeur ajoutée ici. Celui qui n'est applicable qu'à
compiler
temps.
Au lieu d'avoir à écrire plus de code pour vérifier une valeur null, étant
pouvoir
à
déclarer une valeur comme non nullable peut faire gagner du temps et du codage. Si je
décrire un
tapez comme « non nullable », alors il doit être initialisé et éventuellement
affirmé
comme non nul avant d'être transmis à une autre fonction qui attend
non nul.

Comme je l'ai dit plus haut, peut-être que cela pourrait être simplement résolu par un code
contrats
la mise en oeuvre.

Répondez directement à cet e-mail ou consultez-le sur GitHub
<

https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75295204

.

Répondez directement à cet e-mail ou consultez-le sur GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346198

.

Répondez directement à cet e-mail ou consultez-le sur GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-75346390>
.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75347832
.

Vous manquez mes points aussi. Afin de garder votre état cohérent et de pouvoir représenter les valeurs manquantes, vous n'avez pas besoin d'utiliser des valeurs nulles ou indéfinies. Il existe d'autres moyens, en particulier maintenant avec la prise en charge des types d'union, considérez :

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

Nous avons donc simplement encodé une valeur manquante sans utiliser de valeur nulle ou indéfinie. Notez également que nous le faisons de manière 100% explicite . Grâce aux gardes de type, il n'y a aucun moyen de rater une vérification avant de lire une valeur.

À quel point cela est cool?

Pensez-vous qu'une instance de vérification est plus rapide qu'une vérification nulle, pour un
application qui doit en faire des centaines par seconde ?
Permettez-moi de le dire de cette façon : nous avons dû créer des alias pour les fonctions parce que
il est plus rapide de ne pas utiliser '.' accesseurs.
Le 21/02/2015 à 12h58, "Aleksey Bykov" [email protected] a écrit :

Vous manquez mes points aussi. Afin de garder votre état cohérent et
être capable de représenter les valeurs manquantes, vous n'avez pas besoin d'utiliser des valeurs nulles ou
indéfini. Il existe d'autres moyens, surtout maintenant avec la prise en charge des types d'union
envisager:

class Rien { public 'je ne suis rien': Rien; }
classe Quelque chose {
constructeur(
nom public : chaîne,
valeur publique : nombre
) { }
}
var rien = new Rien();
var whoami = Math.random() > 0.5 ? new Something('meh', 66) : rien;

if (whoami instanceof Nothing) {
// whoami n'est rien
console.log('je suis un tueur nul');
} else if (whoami instanceof Something) {
// whoami est une instance de Something
console.log(whoami.name + ': ' + whoami.value);
}

Je viens donc de coder une valeur manquante sans utiliser de valeur nulle ou indéfinie.
Notez également que nous le faisons de manière _100% explicite_. Merci de taper
gardes, il n'y a aucun moyen de rater un contrôle avant de lire une valeur.

À quel point cela est cool?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75349967
.

@aleksey-bykov Notez que j'ai déjà abordé ce point : le besoin de modéliser les définitions de type pour les bibliothèques existantes qui utilisent déjà des valeurs nulles n'est pas résolu. Dans ce cas, vous ne pouvez pas simplement « arrêter d'utiliser » des valeurs nulles.

De plus, cela n'aborde même pas les problèmes avec les variables non initialisées qui seront undefined . En Haskell, le problème n'existe même pas car vous ne pouvez pas laisser une valeur "non initialisée".

@Griffork , je ne pense pas, je fais des tests, les tests disent que cela dépend du navigateur.
http://jsperf.com/nullable-vs-null-vs-undefined-vs-instanceof
Ce que je pense cependant, c'est que vous devez trouver un équilibre entre la sécurité et la performance. Ensuite, vous devez mesurer soigneusement vos performances avant d'essayer de les optimiser. Il y a de fortes chances que vous répariez quelque chose qui n'est pas cassé et vos vérifications nulles qui vous préoccupent tant pourraient constituer moins de 2% de la performance globale, si c'est le cas, si vous doublez plus vite, vous ne gagnerez que 1%.

@spion , tout bien considéré, c'est un moyen beaucoup plus agréable que de continuer à utiliser des valeurs nulles dans votre code, étant donné la situation actuelle et les problèmes que les non-nullables apportent avec eux

Notre base de code comprend plus de 800 fichiers TypeScript et plus de 120 000 lignes de code
et nous n'avons jamais eu besoin de valeurs nulles ou indéfinies lorsqu'il s'agissait de modéliser une entreprise
entités du domaine. Et bien que nous ayons dû utiliser des valeurs NULL pour les manipulations DOM,
tous ces endroits sont soigneusement isolés, de sorte que les nuls n'aient aucun moyen de fuir
Je ne souscris pas à votre argument selon lequel des valeurs nulles pourraient être nécessaires, il y a
langages prêts pour la production sans null (Haskell) ou avec null
interdit (F#).
Le 22 février 2015 à 9h31, "Jon" [email protected] a écrit :

@aleksey-bykov https://github.com/aleksey-bykov D'une pratique
perspective, vous ne pouvez pas ignorer null. Voici quelques informations sur les raisons pour lesquelles vous voudriez
besoin d'un type nullable de toute façon:
https://www.google.com/patents/US7627594?ei=P4XoVPCaEIzjsATm4YGoBQ&ved=0CFsQ6AEwCTge

Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -75437927
.

Oui, et la chose la plus importante que vous puissiez comprendre ici est que nous ne modélisons PAS les entités du domaine commercial, nous SOMMES TRÈS sensibles au facteur temps et notre base de code n'est pas trop petite que la vôtre.

Pour vos besoins, vous n'avez pas besoin de nulls, ce qui est bien, vous pouvez continuer à utiliser Typescript sans eux.
Pour notre objectif, nous avons besoin de valeurs nulles, et nous ne pouvons pas nous permettre de ne pas le faire.

Oh, je viens de voir ton autre commentaire, désolé.
Si vous lisez un peu plus tôt dans ce fil, vous remarquerez que j'ai dit (essentiellement) que nous avons toujours utilisé des valeurs NULL et que nous avons rarement eu des problèmes avec leur infiltration dans d'autres morceaux de code.

Si vous lisez également ci-dessus, la proposition non nullable couvre également undefined.

Null et undefined dans les variables pour leur vitesse semblent similaires, mais dès qu'ils sont présents sur un objet (en particulier un avec une chaîne de prototypes), l'histoire est très différente, nous utilisons donc des nulls à la place.

Encore une fois, cela ne vaut probablement pas la peine de s'inquiéter, à moins que (comme nous) vous fassiez 10 000 vérifications en moins de 10 ms.

(Et oui, nous avons en fait changé en raison du suivi des performances de notre application et de la recherche d'un goulot d'étranglement)

Je voudrais lancer une discussion à ce sujet. Pas plus tard qu'hier, dans la vidéo Build, j'ai vu des analyseurs implémentés en C#. J'ai ouvert un problème #3003 pour implémenter similaire à TypeScript.

Fondamentalement, Analyzer est une bibliothèque qui peut effectuer des vérifications de syntaxe supplémentaires, des balises en tant qu'erreurs/avertissements et qui sont interprétées par le service de langue. Cela permettrait effectivement d'appliquer des contrôles nuls avec une bibliothèque externe.

En fait, je vois que les analyseurs de TypeScript seraient également très utiles pour d'autres choses. Il existe des bibliothèques vraiment étranges, plus que C#.

Pour tous ceux qui suivent ce problème : je maintiens une bibliothèque appelée monapt qui vous permet de déclarer la possibilité de nullité d'une variable. Il comprend des définitions Typescript, donc tout est vérifié au moment de la compilation.

Commentaires bienvenus!

Je viens de lire tout le fil.

Je suppose que la proposition devrait utiliser les termes non-voidable et voidable , car void dans la spécification TS est un type pour la null et undefined . (Je suppose que la proposition couvre également une valeur indéfinie :smile: )

Je suis également d'accord que l'accès non défini ou l'exception nulle est la racine de tous les maux dans la programmation, l'ajout de cette fonctionnalité permettra d'économiser d'innombrables heures pour les gens.

Veuillez ajouter ceci ! Et ce serait mieux si cela devenait une fonctionnalité, je pourrais passer un indicateur pour en faire la valeur par défaut. Cela économiserait beaucoup d'enfer de débogage pour beaucoup de gens.

Bien que je puisse convenir que les ADT peuvent être utilisés à la place des types nullables ; comment pouvons-nous être sûrs que l'ADT que nous passons n'est pas nul lui-même ? N'importe quel type dans TS peut être nul, y compris les ADT, n'est-ce pas ? Je le vois comme un défaut majeur dans la réflexion sur les ADT comme une solution au problème des types non nullables (non annulables). Les ADT ne fonctionnent aussi bien que dans les langages qui autorisent (ou interdisent) les types non nullables.

Un autre problème clé est celui des définitions des bibliothèques. J'ai peut-être une bibliothèque qui attend une chaîne comme argument, mais le compilateur TS peut taper check en passant null à la fonction ! Ce n'est pas juste...

Je le vois comme un défaut majeur dans la réflexion sur les ADT comme une solution au problème...

  1. obtenir des ADT disponibles via un modèle de conception (en raison du manque de
    prise en charge de TypeScript)
  2. interdire le mot clé null
  3. gardez tous les endroits où les nulls peuvent s'infiltrer dans votre code de l'extérieur
  4. profitez du problème de null disparu pour de bon

Sur notre projet, nous avons réussi à faire les étapes 1, 2 et 3. Devinez ce que nous faisons
à présent.

@aleksey-bykov

Comment fais-tu (2) avec undefined ? Cela peut survenir dans diverses situations qui n'impliquent pas le mot-clé undefined : membres de classe non initialisés, champs facultatifs, instructions de retour manquantes dans les branches conditionnelles...

Comparez typescript avec flowtype . Au moins, le flux traite la plupart des cas.

De plus, si vous êtes prêt à dire "Oubliez les idiomes courants dans la langue. Je m'en fiche, je vais m'isoler du reste du monde", alors vous feriez probablement mieux d'utiliser purescript .

Il peut survenir dans une variété de situations qui n'impliquent pas le mot-clé indéfini...

  • membres de classe non initialisés - classes interdites
  • champs optionnels - champs/paramètres optionnels interdits
  • déclarations de retour manquantes - une règle tslint personnalisée qui garantit que tous les chemins d'exécution retournent

mieux vaut utiliser purescript

  • je souhaite mais c'est le code qu'il génère concerne le moins les performances

@aleksey-bykov c'est drôle que vous mentionniez les performances, car le remplacement des types nullables par des ADT impose une surcharge de processeur et de mémoire importante par rapport à la simple application de contrôles null (comme flowtype). Il en va de même pour ne pas utiliser de classes (peut être d'un coût prohibitif si vous instanciez beaucoup d'objets avec beaucoup de méthodes dessus)

Vous mentionnez l'abandon des champs facultatifs, mais ils sont utilisés par de nombreuses bibliothèques JS. Qu'avez-vous fait pour gérer ces bibliothèques ?

Vous mentionnez également ne pas utiliser de classes. Je suppose que vous évitez également les bibliothèques qui s'appuient (ou s'appuieront) sur des classes (comme par exemple React) ?

Dans tous les cas, je ne vois pas de raison d'abandonner autant de fonctionnalités alors qu'une solution parfaitement raisonnable (qui correspond en fait au langage non typé sous-jacent) est très probablement possible à mettre en œuvre.

c'est drôle que vous pensiez que le seul but des ADT est de représenter une valeur manquante qui aurait autrement été codée par null

Je préfère dire que la disparition du problème "nul" (et des classes aussi) est un sous-produit de toutes les bonnes choses que nous apportent ADT

répondre à votre curiosité, pour les éléments critiques pour les performances comme les boucles serrées et les grandes structures de données, nous utilisons ce qu'on appelle l'abstraction Nullable<a> en faisant croire à TypeScript qu'il traite d'une valeur encapsulée, mais en fait (au moment de l'exécution) une telle valeur est juste une primitive qui est autorisée à prendre des valeurs nulles, il existe un ensemble spécial d'opérations sur Nullable qui empêche la fuite de cette valeur nulle

dis moi si tu veux connaitre les details

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

Pourquoi utiliser une structure de type récursif ? 'a nullable': a devrait suffire, non ?

Je suppose que vous avez également callFunctionWithNullableResult<T,U>(f: (T) => U, arg:T):Nullable<U> (y compris toutes les autres surcharges pour les fonctions d'arité différente) pour gérer toutes les fonctions externes qui renvoient des valeurs NULL ?

Et si le mot-clé null est interdit par un linter, comment obtenir un " Nothing " stocké dans un nullable ? faites-vous un cas spécial toNullable(null) ?

Je sais ce que sont les types de données algébriques. Pouvez-vous me donner un exemple (autre que les contrôles d'exhaustivité et la maladresse) où les ADT (ou plutôt les types de somme) + la correspondance de modèle peuvent faire quelque chose que les unions de typescript + les gardes de type définis par l'utilisateur ne peuvent pas faire ?

Pourquoi utiliser une structure de type récursif ? 'a nullable' : a devrait suffire, non ?

eh bien, dans cet exemple, c'est suffisant, mais faire comme ça surcharge le but de ce champ, qui sert 2 cas distincts :

Personnellement, je déteste la surcharge, c'est pourquoi j'utilise 2 pseudo-champs distincts où chacun résout un problème distinct

Je suppose que vous avez également callFunctionWithNullableResult(f: T -> U, arg:T) => Nullable

non, comme je l'ai dit, nous modifions les fichiers de définition (*.d.ts) en remplaçant tout ce dont nous avons la preuve peut être nul dans le code externe par un "wrapper" Nullable , voir l'exemple dans le commentaire dans mon message précédent

comment obtenez-vous un "rien" stocké dans un nullable

nous ne stockons pas les nulls dans les nullables, les nullables proviennent du serveur (dont nous avons un contrôle limité) ou des API externes, dès qu'il est dans notre code, il est enveloppé dans Nullable, mais notre code n'est pas capable de produire un nullable à volonté, nous ne pouvons que transformer un nullable donné en un autre nullable, mais pas en créer un à partir de rien

les unions de typescript + les gardes de type définis par l'utilisateur ne peuvent pas faire

Ha! je ne suis pas du tout fan des types syndicaux et je peux parler pendant des heures de ce qui en fait une mauvaise idée

Revenons à votre question, donnez-moi un équivalent réutilisable de Either a b utilisant des unions et des gardes de type
(un indice : https://github.com/Microsoft/TypeScript/issues/2264)

Les types de votre exemple ne sont pas plus nominaux simplement parce qu'ils sont récursifs - ils restent "pseudo-nominaux".

Pour répondre à votre question cependant : vous n'implémentez pas de Soit générique, précisément parce que les types ne sont pas nominaux. Vous utilisez simplement

type MyType = A | B

puis utilisez des gardes de type pour A et B ... En prime, cela fonctionne également avec toutes les bibliothèques JS existantes : par exemple, la façon idiomatique de vérifier Thenable est s'il y a est une méthode then attachée à l'objet. Un type de garde fonctionnera-t-il pour cela? Certainement.

Ha! C'est une sacrée bonne prise de ta part. Eh bien, quelque chose a dû changer dans les versions récentes du compilateur. Voici le nominal pare-balles :

enum GottaBeNonimalBrand {}
interface GottaBeNonimal {
     branded: GottaBeNonimalBrand;
}
function toGottaBeNominal() : GottaBeNominal {
   return <any> {};
}

Attends une seconde! Vous avez triché ! La bonne vieille méthode fonctionne toujours comme un charme.

Pour répondre à votre question cependant : vous n'implémentez pas de Soit générique, précisément parce que les types ne sont pas nominaux. Vous utilisez simplement...

c'est gentil ce que tu dis, comment est-ce Either c'est Either est GÉNÉRIQUE et il y a environ 30 fonctions de commodité standard autour, maintenant vous dites que je dois aller résolu (nominal) et avoir jusqu'à 30 x number_of_combinations_of_any_2_types_in_my_app de ces fonctions pour toutes les paires possibles que je pourrais rencontrer?

Espérons que ce fil soit toujours surveillé par des personnes de l'équipe TS. Je veux essayer de le redémarrer !
Il me semble que les types non nuls se sont bloqués lorsqu'il est devenu évident qu'il était impossible d'introduire cela de manière incassable. L'équipe C# réfléchit à des types de référence non nullables et est bien sûr confrontée aux mêmes problèmes, notamment de compatibilité. J'aime les idées qu'ils explorent en ce moment et je me demande si elles seraient applicables à TS.

C# vnext remue-méninges (brièvement)

Fondamentalement, en C#, vous avez des types de valeur non nuls, tels que int . Ensuite, dans C# 2.0, nous avons obtenu les types de valeur Nullable, tels que int? . Les références ont toujours été nullables, mais ils envisagent de changer cela et d'appliquer la même syntaxe : string n'est jamais null, string? peut l'être.

Bien sûr, le gros problème est que cela change la signification de string et casse le code. Là où il était possible d'attribuer/retourner null à un string , c'est maintenant une erreur. L'équipe C# envisage de "désactiver" ces erreurs à moins que vous ne l'acceptiez. Le pointage dans string? est toujours une erreur, car la syntaxe est nouvelle et n'était pas autorisée auparavant (il ne s'agit donc pas d'un changement radical).
L'autre problème concerne les autres bibliothèques, etc. Pour résoudre ce problème, ils souhaitent créer l'indicateur d'opt-in par fichier ou assembly. Ainsi, le code externe qui opte pour le système de type plus strict bénéficie des avantages, et les anciens codes et bibliothèques hérités continuent de fonctionner.

Comment cela pourrait-il se transposer à TS ?

  1. Nous introduisons un indicateur d'opt-in de types non nuls. Il peut être global (passé au compilateur) ou par fichier. Il devrait toujours être par fichier pour .d.ts .
  2. Les types de base ne sont pas nullables, par exemple number . Il existe un nouveau type null .
  3. number? est simplement un raccourci pour number | null .
  4. La fonctionnalité introduit de nouvelles erreurs pour les types nullables, comme (x: string?) => x.length sans protection de type.
  5. La fonctionnalité introduit de nouvelles erreurs pour les types non nullables, comme assigner let x: string = null;
  6. Lors de la manipulation d'un type non nul déclaré en dehors de la portée de l'opt-in, les erreurs signalées sous 5. sont ignorées. _Ceci est légèrement différent de dire que string est toujours considéré comme un string? ._

A quoi bon?

Lorsque j'écris un nouveau code, je peux m'inscrire et obtenir des vérifications nulles statiques complètes dans mon code et dans les définitions de bibliothèque mises à jour. Je peux continuer à utiliser les définitions de bibliothèque héritées sans erreur.

Lorsque j'utilise l'ancien code, je peux opter pour de nouveaux fichiers. Je peux déclarer string? toute façon et obtenir des erreurs lors de l'accès aux membres sans une vérification null appropriée. Je bénéficie également d'avantages et de contrôles stricts pour les bibliothèques dont les définitions ont été mises à jour. Une fois qu'un .d.ts inscrit, passer null à une fonction définie comme length(x: string): number devient une erreur.
_(Remarque : La transmission de string? est également une erreur, bien que la transmission de string partir du code de désactivation ne l'est pas.)_

Tant que je ne m'inscris pas, il n'y a pas de changement décisif.

Qu'en penses-tu?

Bien sûr, il y a beaucoup à considérer concernant la façon dont la fonctionnalité fonctionnerait dans la pratique. Mais est-ce que l'équipe TS pourrait même envisager un tel programme d'opt-in ?

c'est incroyable comme les gens veulent un cheval plus rapide au lieu d'une automobile.

Pas besoin d'être condescendant. J'ai lu vos commentaires sur ADT et je ne pense pas qu'ils soient ce dont j'ai besoin. Nous en discuterons peut-être si vous le souhaitez, mais dans un nouveau numéro sur le « support ADT dans TS ». Vous pouvez y écrire pourquoi ils sont géniaux, pourquoi nous en avons besoin et comment ils atténuent le besoin de types non nullables. Ce problème concerne les types non nullables et vous détournez vraiment le sujet.

ce dont vous parlez s'appelle propagation constante et, si elle est implémentée, ne devrait pas être limitée uniquement par 2 constantes aléatoires qui se sont avérées être null et undefined . La même chose peut être faite pour les chaînes, les nombres et tout le reste et de cette façon, cela a du sens, sinon ce serait juste pour nourrir quelques vieilles habitudes que certaines personnes ne sont pas prêtes à tout à fait

@aleksey-bykov et bien sûr, pour obtenir de meilleures restrictions de type, vous implémentez des types de type supérieur et pour obtenir des instances dérivées, vous implémentez des classes de types, etc. Comment cela correspond-il à l'objectif de conception « aligner avec ES6+ » de TypeScript ?

Et c'est toujours inutile pour résoudre le problème d'origine de ce problème. Il est facile d'implémenter Maybe (et l'un ou l'autre) dans TS en utilisant des classes génériques, même aujourd'hui (sans types d'union). Ce sera à peu près aussi utile que l'ajout de Optional à Java, c'est-à-dire à peine. Parce que quelqu'un affectera simplement null quelque part ou aura besoin de propriétés facultatives, et le langage lui-même ne se plaindra pas mais les choses exploseront au moment de l'exécution.

Je ne comprends pas pourquoi vous insistez pour abuser du langage pour le faire plier d'une manière pour laquelle il n'a jamais été conçu. Si vous voulez un langage fonctionnel avec des types de données algébriques et aucune valeur nulle, utilisez simplement PureScript. C'est un langage excellent et bien conçu avec des fonctionnalités qui s'emboîtent de manière cohérente, testé au combat au fil des ans dans Haskell sans les verrues accumulées de Haskell (hiérarchie des classes de caractères, enregistrements réels avec polymorphisme des lignes...). Il me semble qu'il est beaucoup plus facile de commencer avec un langage qui adhère à vos principes et à vos besoins, puis de le peaufiner pour la performance, plutôt que de commencer avec quelque chose qui ne fonctionne pas (et n'a jamais été censé le faire), puis tournez, tournez et restreignez pour l'obtenir travaillez à votre façon.

Quel est votre problème de performances avec PureScript ? Est-ce les fermetures produites par le curry omniprésent ?

@jods4 cela signifie-t-il que tous les fichiers .d.ts actuellement existants devront être mis à jour avec un indicateur d'arrêt de travail, ou que le paramètre à l'échelle du projet n'affecte pas .d.ts (et un .d.ts sans drapeau est toujours supposé être l'ancienne méthode.
Dans quelle mesure est-ce compréhensible (cela va-t-il causer des problèmes aux personnes travaillant sur plusieurs projets ou de la confusion lors de la lecture des fichiers .d.ts) ?
À quoi ressemblera l'interface entre le nouveau et l'ancien code (bibliothèque). Sera-t-il jonché d'un tas de vérifications et de moulages inutiles (dans certains cas) (probablement pas une mauvaise chose, mais cela pourrait dissuader les débutants de la langue) ?
La plupart du temps, j'aime (maintenant) l'idée des types non nullables, et la suggestion de @jods4 et la suggestion ! est la seule façon dont je vois les types non nullables fonctionner (en ignorant les adts).

C'est peut-être perdu dans ce fil, mais quel problème cela résout-il? C'est quelque chose qui m'a fait allusion. Les arguments que j'ai vus récemment dans ce fil sont "d'autres langues l'ont". J'ai également du mal à voir ce que cela résout qui est utile qui ne nécessite pas une sorte de machination sur TypeScript. Bien que je puisse comprendre que les valeurs null et undefined peuvent être indésirables, j'ai tendance à voir cela comme une logique d'ordre supérieur que je veux mettre dans mon code plutôt que d'attendre que le langage sous-jacent le gère .

L'autre petit problème, c'est que tout le monde traite undefined comme une constante ou un mot-clé. Techniquement non plus. Dans le contexte global, il existe une variable/propriété undefined qui est de type primitif undefined . null est un littéral (qui se trouve également être du type primitif null , bien que ce soit un bogue de longue date qu'il soit implémenté pour renvoyer typeof "object" ). Dans ECMAScript, ce sont deux choses assez différentes.

@kitsonk
Le problème est que tous les types peuvent être nuls et que tous les types peuvent être indéfinis, mais toutes les opérations qui fonctionnent sur un type spécifique ne fonctionnent pas sur des valeurs nulles ou indéfinies (par exemple, diviser) provoquant des erreurs.
Ce qu'ils veulent, c'est la possibilité de spécifier un type comme « est un nombre et n'est jamais nul ou indéfini » afin qu'ils puissent s'assurer que lorsqu'ils utilisent une fonction qui a la possibilité d'introduire des programmeurs nuls ou indéfinis, sachez se prémunir contre les valeurs invalides.
Il y a deux arguments principaux pour pouvoir spécifier une valeur comme « est-ce de ce type mais ne peut pas être nul ou indéfini » :
A) il est évident pour tous les programmeurs utilisant le code que cette valeur ne doit jamais être nulle ou indéfinie.
B) le compilateur aide à détecter les gardes de type manquants.

il existe une variable/propriété undefined qui est du type primitif undefined. null est un littéral (qui se trouve également être du type primitif null

@kitsonk c'est un excellent argument pour ce problème ; null n'est pas un number , ce n'est pas plus un Person qu'un Car . C'est son propre type et ce problème concerne le fait de permettre au tapuscrit de faire cette distinction.

@kitsonk Je pense que l'une des erreurs les plus courantes est les erreurs indéfinies de référence/accès null. Avoir la possibilité de spécifier une propriété ou une variable pour qu'elle ne soit pas nulle/non définie rendra le code logiciel plus solide. Et laissez également des outils tels que LS détecter des bogues comme celui-ci https://github.com/Microsoft/TypeScript/issues/3692.

Merci pour la clarification. Peut-être qu'il me manque encore quelque chose ici, après avoir parcouru cela... Puisqu'il existe déjà un argument "optionnalité" dans TypeScript, pourquoi ce qui suit ne fonctionnerait-il pas comme une proposition?

interface Mixed {
    optional?: string;
    notOptional: string;
    nonNullable!: string;
}

function mixed(notOptional: string, notNullable!: string, optional?: string): void {}

Bien qu'il y ait une plus grande fonctionnalité dans certaines des solutions proposées, je soupçonne que la protection du type contre eux devient un défi, alors que cela (bien que je ne connaisse pas bien le fonctionnement interne de TypeScript) semble être une extension de ce qui est déjà vérifié.

Puisqu'il existe déjà un argument « optionnalité » dans TypeScript, pourquoi ce qui suit ne fonctionnerait-il pas en tant que proposition ?

Juste pour clarifier les options en ce moment dans TS ne sont facultatives que lors de l'initialisation. Null et undefined sont toujours assignables à tous les types.

Ce sont des cas peu intuitifs d'options

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

Ou pour rester simple, optional dans TS signifie non initialisable. Mais cette proposition dit qu'undefined et null ne peuvent pas être attribués à un type non voidable (non null, non undefined).

Le ? est placé à côté du nom de la propriété/de l'argument car il fait référence à l'_existence_ de ce nom ou non. @tinganho l'a bien expliqué. Prolongeant son exemple :

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

Le ! non nul n'est pas lié à l'_existence_ d'un nom. Il est lié au type, c'est pourquoi il doit être placé par type. Le type n'est pas nullable.

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 Les .d.ts existants fonctionneraient correctement et peuvent être mis à jour en douceur pour prendre en charge des définitions plus strictes.
Par défaut, .d.ts n'accepte pas. Si un auteur de bibliothèque met à jour sa bibliothèque avec des définitions fidèles non NULL, il l'indique en plaçant un indicateur en haut du fichier.
Dans les deux cas, tout fonctionne sans aucune réflexion/configuration du consommateur.

@kitsonk Le problème que cela résout est de réduire les bogues car les gens supposent que quelque chose n'est pas nul alors qu'il pourrait l'être, ou passent des valeurs nulles dans des fonctions qui ne le prennent pas en charge. Si vous travaillez sur de grands projets avec de nombreuses personnes, vous pouvez utiliser des fonctions que vous n'avez pas écrites. Ou vous pouvez utiliser des bibliothèques tierces que vous ne connaissez pas bien. Lorsque vous faites : let x = array.sum(x => x.value) , pouvez-vous supposer que x n'est jamais nul ? Peut-être que c'est 0 si le tableau est vide ? Comment le sais-tu? Votre collection est-elle autorisée à avoir x.value === null ? Est-ce pris en charge par la fonction sum() ou s'agira-t-il d'une erreur ?
Avec cette fonctionnalité, ces cas sont vérifiés de manière statique et le passage d'une valeur éventuellement nulle à une fonction qui ne la prend pas en charge ou la consommation d'une valeur éventuellement nulle sans vérification sont signalés comme des erreurs.
Fondamentalement, dans un programme complètement typé, il ne peut plus y avoir de "NullReferenceException" (moins les cas particuliers liés à undefined , malheureusement).

La discussion sur les paramètres facultatifs n'est pas liée. Autoriser null ou non est lié au système de types, pas aux déclarations de variables/paramètres et il s'intègre parfaitement avec les gardes de type, les types d'union et d'intersection, etc.

le type _void_ n'a aucune valeur mais les valeurs null et undefined sont assignables

voici un bon résumé : https://github.com/Microsoft/TypeScript/issues/185#issuecomment -71942237

@jbondc Je pense avoir vérifié la spécification récemment. void est un terme générique pour null et undefined . Je ne sais pas pour void 0 , mais je suppose que void aussi sous son égide.

Déduit any . mais null et undefined est toujours assignable à tous les types.

void 0 renvoie toujours undefined également http://stackoverflow.com/a/7452352/449132.

@jbondc si la question portait sur un système de type nul potentiel tel que celui que j'ai décrit ci-dessus,

function returnsNull() {
  return null;
}

devrait déduire le type null .

Il y a beaucoup de cas de coin autour de undefined et pour être honnête, je ne suis pas (encore ?) sûr de la meilleure façon d'y faire face. Mais avant de passer beaucoup de temps à y penser, j'aimerais savoir si l'équipe TS serait d'accord avec une stratégie d'opt-in.

La dernière fois que j'ai participé à cette discussion, elle s'est terminée par : "nous ne ferons pas de changements de rupture et nous ne voulons pas changer la signification du code source en fonction des drapeaux/options ambiants". L'idée que je décris ci-dessus n'est pas un changement radical et est relativement bonne en ce qui concerne l'interprétation de la source, bien que pas à 100%. Cette zone grise est la raison pour laquelle je demande ce qu'en pense l'équipe TS.

@jods4 Je préférerais le any , qui conserverait la syntaxe existante. Et une stratégie opt-in serait préférable. [1]

Je préférerais une syntaxe explicite pour les types non nullables. Pour les objets, cela casserait un peu trop de choses à la fois. Et aussi, les arguments facultatifs devraient toujours être nullables pour des raisons évidentes (et il devrait être impossible de les changer). Les arguments par défaut doivent conserver leur signature actuelle en externe, mais à l'intérieur de la fonction elle-même, l'argument peut être rendu non nullable.

Je trouve cela absurde, cependant, car les nombres nullables cassent beaucoup des optimisations que les moteurs peuvent faire, et ce n'est pas un cas d'utilisation courant en dehors des arguments facultatifs/par défaut. Il pourrait être un peu plus faisable/incassable de faire en sorte que les nombres qui ne sont pas des arguments facultatifs soient non nullables par défaut via un indicateur de compilateur. Cela nécessiterait également une syntaxe pour marquer explicitement les types nullables, mais je le vois déjà comme le résultat probable de cette discussion.

[1] L'introduction d'un indicateur pour en faire la valeur par défaut ne fonctionnerait pas bien en premier lieu pour des raisons pratiques. Examinez simplement les problèmes rencontrés par les utilisateurs avec --noImplicitAny , ce qui a entraîné des risques de migration, de nombreux problèmes pratiques lors du test de l'existence de propriétés menant à --suppressImplicitAnyIndexErrors , et plusieurs définitions DefinitelyTyped cassées.

@impinball

Je préférerais le nullable inféré any , qui conserverait la syntaxe existante. Et une stratégie opt-in serait préférable.

J'ai édité ma réponse ici. En y réfléchissant davantage, je ne vois tout simplement aucun cas utile où vous pouvez implicitement déduire le type null uniquement. Comme : () => null ou let x = null ou function y() { return null } .

Je suis donc d'accord pour continuer à déduire que any est probablement une bonne chose.

  1. C'est rétrocompatible.
  2. Tous ces cas sont des erreurs inférieures à noImplicitAny toute façon.
  3. Si vous avez un cas étrange où vous voulez faire cela et que cela vous est utile, vous pouvez déclarer explicitement le type : (): null => null ou let x: null = null ou function y(): null { return null } .

Je préférerais une syntaxe explicite pour les types non nullables. Pour les objets, cela casserait un peu trop de choses à la fois.

Dire que string signifie en réalité que string! | null peut être une autre option. Il convient de noter que si vous lisez toutes les discussions ci-dessus, cela a d'abord été proposé car on espérait qu'il serait plus compatible avec le code existant (ne changeant pas la signification actuelle de string ). Mais en fait, la conclusion était que même ainsi, d'énormes quantités de code seraient brisées de toute façon.

Étant donné que l'adoption d'un système de type non nullable ne sera pas un changement trivial pour les bases de code existantes, je choisirais l'option string? plutôt que string! car elle est plus propre. Surtout si vous regardez le code source du compilateur TS, où à peu près tous les types internes seraient nommés de manière inexacte :(

Il pourrait être un peu plus faisable/incassable de faire en sorte que les nombres qui ne sont pas des arguments facultatifs soient non nullables par défaut via un indicateur de compilateur. Cela nécessiterait également une syntaxe pour marquer explicitement les types nullables, mais je le vois déjà comme le résultat probable de cette discussion.

Je pense que les choses deviennent confuses à ce stade. Pour moi, cela semble être un bon argument pour utiliser string? partout. Je n'aimerais pas voir mélanger number? et string! , cela deviendrait trop déroutant trop rapidement.

[1] L'introduction d'un indicateur pour en faire la valeur par défaut ne fonctionnerait pas bien en premier lieu pour des raisons pratiques.

Oui, cela ne fonctionnerait pas bien pour une base de code existante sans modifications. Mais:

  1. Cela fonctionnera très bien pour les nouvelles bases de code. Nous pouvons construire un avenir meilleur avec cela.
  2. Les bases de code existantes récolteront _quelques_ avantages et des contrôles supplémentaires, même si elles ne s'y inscrivent jamais.
  3. Les bases de code existantes peuvent être acceptées par fichier, ce qui permet au nouveau code d'être plus strict et peut permettre une transition progressive de l'ancien code, si vous le souhaitez.

@jods4

Je pense que les choses deviennent confuses à ce stade. Pour moi, cela semble être un bon argument pour utiliser une chaîne? partout. Je n'aimerais pas voir un mélange de numéros ? et string!, cela deviendrait trop déroutant trop rapidement.

Je ne le pensais pas dans ce sens. Je voulais dire qu'il y a moins de cas d'utilisation pour les nombres nullables que pour les chaînes nullables. Je ne suis pas si attaché à cette suggestion, de toute façon (je m'en fiche de toute façon).

Les bases de code existantes peuvent être acceptées par fichier, ce qui permet au nouveau code d'être plus strict et peut permettre une transition progressive de l'ancien code, si vous le souhaitez.

Avec quels moyens ? Je ne pense pas que vous puissiez actuellement configurer le compilateur fichier par fichier. Je suis ouvert à avoir tort ici.

@impinball

Avec quels moyens ? Je ne pense pas que vous puissiez actuellement configurer le compilateur fichier par fichier. Je suis ouvert à avoir tort ici.

Peut-être que je me trompe. Je ne les ai jamais utilisés mais lorsque je regarde les cas de test, je vois beaucoup de commentaires ressemblant à // <strong i="9">@module</strong> amd . J'ai toujours supposé que c'était un moyen de spécifier des options à partir d'un fichier ts. Je n'ai jamais essayé de le faire, alors peut-être que je me trompe complètement ! Regardez par exemple ici :
https://github.com/Microsoft/TypeScript/blob/master/tests/cases/conformance/externalModules/amdImportAsPrimaryExpression.ts

Si ce n'est pas un moyen de spécifier des options par fichier, nous aurons peut-être besoin de quelque chose de nouveau. Il est obligatoire de spécifier cette option par fichier, _au moins_ pour les fichiers .d.ts . Un commentaire formaté spécial en haut du fichier pourrait faire l'affaire, après tout, nous avons déjà un support pour /// <amd-dependency /> et co.

EDIT : je me trompe. La spéléologie du code source m'a montré que ces éléments sont appelés marqueurs et pré-analysés par le coureur FourSlash. C'est donc uniquement pour les tests, ce n'est pas à l'intérieur du compilateur. Il faudrait trouver quelque chose de nouveau pour cela.

Il existe également des limitations pratiques dans certains cas - ce n'est pas le plus pratique pour certains arguments comme les fonctionnalités ES6, car cela nécessiterait une réanalyse complète du fichier. Et IIUC la vérification de type est effectuée dans le même passage que la création AST en premier lieu.

Si vous regardez le code de l'analyseur, les commentaires /// <amd-dependency /> et /// <amd-module /> sont lus avant que quoi que ce soit d'autre ne soit analysé dans un fichier. Ajouter quelque chose comme /// <non-null-types /> serait faisable. OK, c'est un nom terrible et je ne suis pas pour la prolifération d'options "magiques" arbitraires, mais ce n'est qu'une idée.

@jods4 Je pense que 'm pinball dit que les IDE ne le prendront pas en charge sans de grands changements dans le fonctionnement de la coloration syntaxique.

Y aura-t-il une forme de type non nullable en 2.0 ?
Après tout, le tapuscrit apporte très moins de choses au javascript s'il ne peut pas garantir le type. Que pouvons-nous obtenir après avoir surmonté tous les problèmes et réécritures introduits par le tapuscrit ?
Un meilleur IntelliSense ? Étant donné que vous devez toujours vérifier le type manuellement ou par code dans chaque fonction, exportez vers d'autres modules.

Bonne nouvelle à tous, TypeScript 1.6 a suffisamment de puissance expressive pour modéliser et suivre en toute sécurité une valeur manquante (qui sinon sont codées par des valeurs null et undefined ). Essentiellement, le modèle suivant vous donne des types non nullables :

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

Cela dit, la demande devrait être fermée car aucun problème n'existe plus. Affaire classée, classe rejetée.

@aleksey-bykov

Cela dit, la demande devrait être fermée car aucun problème n'existe plus.

Vous ne pouvez pas être sérieux, n'est-ce pas ? Nous vous avons dit à plusieurs reprises qu'un modèle pour le type algébrique nullable n'était pas à l'origine de ce problème.

Je ne vais _pas_ envelopper chaque var x: number de mon code dans un Nullable<number> ou encore mieux NonNullable<x> . C'est juste trop de frais généraux. Pourtant, je veux savoir que faire x *= 2 est 100% sûr, partout où cela se produit dans mon code.

Votre code ne résout pas le problème de l'utilisation d'une bibliothèque tierce. Je ne vais _pas_ appeler sanitize sur _chaque_ bibliothèque tierce, ou même l'API DOM intégrée, que j'appelle. De plus, je ne veux pas ajouter isNothing(safe) _partout_. Ce que je veux, c'est pouvoir appeler let squares = [1,2,3].map(x => x*x) et être sûr à 100% au moment de la compilation que squares.length est sûr. Avec _any_ API que j'utilise.

De même, je veux une documentation pour les bibliothèques JS tierces qu'une fonction accepte null comme entrée ou non. Je veux savoir au moment de la compilation si $(element).css(null) est OK ou une erreur.

Je veux travailler dans des environnements d'équipe, où je ne peux pas garantir que tout le monde utilise des modèles complexes comme le vôtre de manière cohérente. Votre type Nulllable ne fait absolument rien pour empêcher un développeur de faire let x: number = null; x.toString() (ou quelque chose de moins stupide, mais dans le même sens).

Et ainsi de suite. Ce ticket est _loin_ d'être fermé et le problème est toujours là à 100%.

Vous ne pouvez pas être sérieux, n'est-ce pas ?

Je suis assez sérieux.

Je ne vais pas tout emballer...

Pourquoi pas? Avec la syntaxe ! ou tout ce que vous poussez, vous devriez le faire de toute façon.

ou encore mieux NonNullable

Cela devrait être Nullable, ce que nous une valeur non nullable ou null et cela est explicitement indiqué. Contrairement aux types conventionnels qui peuvent TOUJOURS avoir une valeur ou null et être appelés number ce qui implique que vous devriez le vérifier pour null avant de l'utiliser.

C'est juste trop de frais généraux.

Où?

Votre code ne résout pas le problème de l'utilisation d'une bibliothèque tierce.

Cela fait.

Je ne vais pas appeler sanitize sur toutes les bibliothèques tierces, ni même sur l'API DOM intégrée que j'appelle.

Vous n'êtes pas obligé. Tout ce que vous avez à faire est de corriger le fichier de définition de cette bibliothèque tierce en remplaçant tout ce qui peut être nul par Nullable<*>

De même, je veux une documentation pour les bibliothèques JS tierces qu'une fonction accepte null comme entrée ou non.

Même chose. Définissez cette méthode comme acceptant Nullable<string> au lieu d'un simple string .

Votre type Nullable ne fait absolument rien pour empêcher un développeur de faire let x: number = null; x.toString()

Ce n'est pas le cas en effet. Vous devez bannir le mot-clé null utilisant le linter.

Allez, ma solution est à 95% fonctionnelle et 100% pratique et elle est disponible AUJOURD'HUI sans avoir à prendre de décisions difficiles et à mettre tout le monde sur la même longueur d'onde. C'est la question que vous recherchez : une solution de travail qui ne ressemble pas à ce que vous attendez mais qui fonctionne néanmoins, ou l'obtenir exactement comme vous le souhaitez avec tout le dos et les cerises sur le dessus

Dans 1.4 et 1.5, le type void n'autorise aucun membre d'Object, y compris .toString(), donc type Nothing = void; devrait être suffisant au lieu d'avoir besoin du module (à moins que cela ne change à nouveau dans 1.6). http://bit.ly/1OC5h8d

@aleksey-bykov ce n'est pas vraiment vrai. Vous devez toujours faire attention à nettoyer toutes les valeurs éventuellement nulles. Vous ne recevrez pas d'avertissement si vous oubliez de le faire. C'est une amélioration, c'est sûr (moins d'endroits à surveiller).

Aussi, pour illustrer comment les hacks ne vous mèneront pas très loin, essayez simplement

if (isNothing(something)) {
  console.log(something.toString())
}

Je ne vais pas tout emballer...

Pourquoi pas? Avec ! syntaxe ou quoi que ce soit d'autre que vous poussez, vous devrez le faire de toute façon.

OK, je _pourrais_ le faire si tout le reste est résolu. Et si tout est résolu, peut-être que TS peut même envisager un peu de sucre de langue pour cela.

ou encore mieux NonNullable

Cela devrait être Nullable, ce que nous modélisons, ce sont des types nullables qui peuvent avoir une valeur non nullable ou null et cela est explicitement indiqué. Contrairement aux types conventionnels qui peuvent TOUJOURS avoir une valeur ou null et être appelés nombre, ce qui implique que vous devriez le vérifier pour null avant de l'utiliser.

Ce n'est pas que je veuille juste me débarrasser des exceptions nulles. Je veux aussi pouvoir exprimer des types non nullables, avec une sécurité statique.
Supposons que vous ayez une méthode qui renvoie toujours une valeur. Comme toString() renvoie toujours une chaîne non nulle.
Quelles sont vos options là-bas?
let x: string = a.toString();
Ce n'est pas bon car il n'y a pas de validation statique que x.length est sûr. Dans ce cas, c'est le cas, mais comme vous l'avez dit, le string intégré pourrait bien être null , c'est donc le _statu quo_.
let x = sanitize(a.toString());
OK maintenant, je ne peux pas l'utiliser sans une vérification nulle, donc le code est sûr. Mais je ne veux pas ajouter if (isNothing(x)) partout où j'utilise x dans mon code ! C'est à la fois moche et inefficace, car je pourrais très bien savoir que x n'est pas nul au moment de la compilation. Faire (<string>x).length est plus efficace mais c'est quand même moche de devoir le faire partout où vous voulez utiliser x .

Ce que je veux faire c'est :

let x = a.toString(); // documented, non-null type string (string! if you want to)
x.length; // statically OK

Vous ne pouvez pas y parvenir sans une prise en charge linguistique appropriée, car tous les types dans JS (et TS 1.6) sont toujours nullables.

Je voudrais dire que la programmation avec des types non facultatifs (nullables) est une très bonne pratique, autant que possible. Donc ce que je décris ici est un scénario essentiel, pas une exception.

C'est juste trop de frais généraux.

Où?

Voir ma réponse précédente.

Votre code ne résout pas le problème de l'utilisation d'une bibliothèque tierce.

Cela fait.

Seulement la moitié du problème est résolu. En supposant que les définitions de bibliothèque ont été mises à jour comme vous l'avez proposé : chaque entrée ou valeur renvoyée nullable est remplacée par Nullable<X> au lieu de X.

Je peux toujours passer null à une fonction qui n'accepte pas les paramètres null . OK, ce fait est maintenant _documenté_ (ce que nous pouvons déjà faire avec les commentaires JSDoc ou autre) mais je veux qu'il soit _appliqué_ au moment de la compilation.
Exemple : declare find<T>(list: T[], predicate: (T) => bool) . Les deux paramètres ne doivent pas être nuls (je n'ai pas utilisé Nullable ) mais je peux faire find(null, null) . Une autre erreur possible est que la déclaration dit que je devrais retourner un bool non nul find([], () => null) .

Votre type Nullable ne fait absolument rien pour empêcher un développeur de faire let x: number = null; x.toString()

Ce n'est pas le cas en effet. Vous devez interdire le mot-clé null en utilisant le linter.

Le faire et fournir une variable globale nothing la place ne vous aidera pas dans les cas que j'ai énumérés dans le point précédent, n'est-ce pas ?

De mon point de vue, ce n'est pas 95%. J'ai l'impression que la syntaxe est devenue bien pire pour de nombreuses choses courantes et je n'ai toujours pas toute la sécurité statique que je veux quand je parle de types non nullables.

Vous devez toujours faire attention à nettoyer toutes les valeurs éventuellement nulles.

Juste le même genre de travail de séparation que vous auriez à faire de toute façon avec des types hypothétiques non nullables dont vous parlez ici. Vous devrez revoir tous vos fichiers de définitions et mettre ! là où c'est approprié. En quoi est-ce différent ?

Vous ne recevrez pas d'avertissement si vous oubliez de le faire.

Même chose. Même chose.

illustrer comment les hacks ne vous mèneront pas vraiment très loin, essayez simplement

Bien que vous ayez raison avec la façon dont j'ai défini Nothing qui a toString de Object , mais.. si nous prenons l'idée de @Arnavion (d'utiliser void pour Nothing ) tout s'enclenche soudainement

Regardez

image

De mon point de vue, ce n'est pas 95%. J'ai l'impression que la syntaxe est devenue bien pire pour de nombreuses choses courantes et je n'ai toujours pas toute la sécurité statique que je veux quand je parle de types non nullables.

mec, vous venez de me convaincre, ce modèle n'est pas pour vous et ne le sera jamais, s'il vous plaît ne l'utilisez jamais dans votre code, continuez à demander les vrais types non nullables, bonne chance, désolé de vous déranger avec de telles bêtises

@aleksey-bykov
Je vois ce que vous essayez de faire, même s'il me semble que vous n'avez pas encore réglé tous les cas et que vous semblez inventer des solutions en cours de route lorsque quelqu'un signale un trou (comme la substitution de null par void dans le dernier exemple).

En fin de compte _peut-être_ vous le ferez fonctionner comme je le souhaite. Vous utiliserez une syntaxe complexe pour les types nullables, vous aurez banni avec un linter toute source de null dans le programme qui peut éventuellement rendre les types nullables non nuls. En cours de route, vous devrez convaincre tout le monde de mettre à jour leurs bibliothèques en utilisant votre convention non standard, sinon cela ne sert à rien (attention, c'est vrai pour _toutes_ les solutions au problème nul, pas seulement la vôtre).

À la toute fin, vous pouvez réussir à tordre le système de type pour éviter le type null intégré et avoir les vérifications que nous voulons attachées à votre type Nothing .

À ce stade, ne pensez-vous pas que le langage devrait simplement le prendre en charge ? Le langage pourrait tout implémenter à peu près de la même manière que vous le souhaitez (en interne). Mais en plus de cela, vous aurez une belle syntaxe, des cas extrêmes aplanis, pas besoin de linters externes et sûrement une meilleure adoption par des définitions tierces. Cela n'a-t-il pas plus de sens ?

À ce stade, ne pensez-vous pas que le langage devrait simplement le prendre en charge ?

Parlons de ce que je pense. Je pense que ce serait bien de se réveiller dans le monde où TypeScript a demain des types non nullables. En fait, c'est la pensée avec laquelle je me couche tous les soirs. Mais cela n'arrive jamais le lendemain et je suis frustré. Ensuite, je me mets au travail et rencontre le même problème avec les valeurs nulles encore et encore jusqu'à récemment, lorsque j'ai décidé de rechercher un modèle qui pourrait me faciliter la vie. On dirait que je l'ai trouvé. Est-ce que je souhaite toujours que nous ayons des non-nullables dans TypeScript ? Bien sûr, je le fais. Puis-je vivre sans eux et sans frustration ? On dirait que je peux.

Est-ce que je souhaite toujours que nous ayons des non-nullables dans TypeScript ? Bien sûr, je le fais.

Alors pourquoi voulez-vous clore ce problème ? :souriant:

Cela dit, la demande devrait être fermée car aucun problème n'existe plus. Affaire classée, classe rejetée.

Je suis heureux que vous ayez trouvé une solution à vos besoins, mais comme vous, j'espère toujours qu'un jour TS bénéficiera d'une assistance appropriée. Et je crois que cela peut encore arriver (pas de sitôt, remarquez). L'équipe C# fait actuellement les mêmes brainstormings et cela peut peut-être aider à faire avancer les choses. Si C# 7 parvient à obtenir des types non nuls (ce qui n'est pas encore sûr), il n'y a aucune raison pour que TS ne fasse pas de même.

@aleksey-bykov

Alors pour simplifier

var nothing: void = null;
function isNothing<a>(value: a | void): value is void {
    return value == null;
}
var something = Math.random() > 0.5 ? 'hey!' : nothing;

et maintenant vous pouvez l'utiliser sans aucun wrapper de fonction - des définitions de type appropriées sont suffisantes.

C'est fondamentalement la solution de contournement qui m'a fait à l'origine un peu heureux jusqu'à ce que découragé de m'y fier .

Il faudrait revoir tous vos fichiers de définitions et mettre ! le cas échéant. En quoi est-ce différent ?

La principale différence lors de l'utilisation de fonctionnalités de langage officiel standard au lieu de hacks est que la communauté (DefinitelyTyped) est là pour aider à écrire, tester et partager les fichiers de définition au lieu que tout ce travail supplémentaire soit empilé sur vos projets (enfin, vraiment :le sourire:).

Je plaide pour " --noImplicitNull ". Cela rendrait tous les types par défaut non nullables, donnant immédiatement des commentaires sur les problèmes potentiels dans tout code existant. S'il y a "!", il devrait se comporter comme Swift pour faire en sorte que le vérificateur de type ignore la possibilité de nulls (transtypage non sécurisé en non nullable) - bien que cela soit en conflit avec l' opérateur ES7 + "eventual send" sur les promesses, donc ce n'est peut-être pas le meilleur idée.

Si cela semble trop radical en termes de rupture de compatibilité descendante, c'est vraiment de ma faute. Je devrais vraiment essayer de trouver le temps et l'essayer dans une fourchette pour prouver que ce n'est pas vraiment aussi radical qu'il y paraît. En fait en ajoutant "!" est plus radical : il faudrait plus de changements pour en profiter car la plupart des valeurs ne sont pas vraiment nulles ; --noImplicitNull est plus progressif car vous découvrirez plus d'erreurs en corrigeant les définitions de type qui revendiquent de manière incorrecte des valeurs non nulles et sans ces correctifs, vous obtiendrez le même comportement à l'exception de l'affectation nulle, des valeurs non initialisées et des vérifications oubliées pour les champs d'objet facultatifs .

En ajoutant ma voix à la discussion, j'aime la façon dont Kotlin a résolu le problème, c'est-à-dire

  • les propres variables n'ont pas de types nullables par défaut, un '?' des changements qui
  • les variables externes sont nullables, un '!!' remplace
  • une vérification nulle est un transtypage de type
  • le code externe peut être analysé par un outil qui génère des annotations

Comparer:
1
2

  • si a|void est banni on peut passer à a|Nothing

La principale différence lors de l'utilisation de fonctionnalités de langue officielle standard au lieu de hacks est que la communauté (DefinitelyTyped) est là pour aider à écrire, tester et partager les fichiers de définition

  • Je ne vois pas pourquoi utiliser des fonctionnalités 100% légitimes est un hack
  • ne surestimez pas les compétences de la communauté, de nombreuses (sinon la plupart) définitions sont de mauvaise qualité et même celles comme, disons, jquery ne peuvent souvent pas être utilisées telles qu'elles sont écrites sans quelques modifications
  • encore une fois, un modèle ne sera jamais aussi bon qu'une fonctionnalité à part entière, j'espère que tout le monde l'aura, sans aucun doute
  • un modèle peut résoudre vos problèmes aujourd'hui alors qu'une caractéristique peut être proche ou non
  • si une situation peut être résolue efficacement (jusqu'à 95 %) en utilisant AB et C, pourquoi quelqu'un aurait-il besoin de D pour faire de même ? envie de sucre ? pourquoi ne nous concentrerions-nous pas sur quelque chose qui ne peut pas être résolu par un modèle ?

de toute façon, il y a des résolveurs de problèmes et des perfectionnistes, comme cela a été montré le problème est résoluble

(Si vous ne voulez pas vous fier au type void, Nothing peut également être class Nothing { private toString: any; /* other Object.prototype members */ } qui aura le même effet. Voir #1108)

_Remarque : rien ici ne concerne le bikeshedding de la syntaxe. S'il te plait ne le prends pas
en tant que tel._

Je vais le dire simplement : voici le problème avec la solution TS 1.6 proposée
ici : cela peut fonctionner, mais cela ne s'applique pas à la bibliothèque standard ou
la plupart des fichiers de définition. Cela ne s'applique pas non plus aux opérateurs mathématiques. Si tu veux
pour vous plaindre du type redondant/type d'exécution, essayez de faire du générique
les collections fonctionnent en C++ - cela n'est rien en comparaison, surtout si vous voulez
itération paresseuse ou collections qui ne sont pas des tableaux (ou pire encore, essayez
combinant les deux).

Le problème avec la solution actuellement proposée par @aleksey-bykov est que
ce n'est pas appliqué dans le noyau de la langue. Par exemple, vous ne pouvez pas faire
Object.defineProperty(null, "name", desc) - vous obtiendrez une TypeError de
l'objet cible null . Autre chose : vous ne pouvez attribuer aucune propriété à un
null objet, comme dans var o = null; o.foo = 'foo'; . Vous obtiendrez un
ReferenceError IIRC. La solution proposée ne peut pas tenir compte de ces cas.
C'est pourquoi nous avons besoin d'un support linguistique pour cela.


_Maintenant, un peu de bikeshedding doux..._

Et pour ce qui est de la syntaxe, j'aime la concision du "?string" et
Syntaxe "!string", mais en ce qui concerne le résultat final, je m'en fiche tant que je suis
ne pas écrire ThisIsANullableType<T, U, SomeRidiculousAndUnnecessaryExtraGeneric<V, W>> .

Le mercredi 29 juillet 2015, à 16 h 10, Aleksey Bykov [email protected] a écrit :

  • si a|void est interdit, on peut passer à a|Rien

    La principale différence lors de l'utilisation de fonctionnalités de langue officielle standard
    au lieu de hacks, c'est que la communauté (DefinitelyTyped) est là pour aider
    écrire, tester et partager les fichiers de définition

    Je ne vois pas pourquoi utiliser des fonctionnalités 100% légitimes est un hack

    ne surestimez pas les compétences de la communauté, beaucoup (sinon la plupart)
    il y a des définitions de mauvaise qualité et même celles comme, disons, jquery assez

    ne peut souvent pas être utilisé tel qu'il est écrit sans quelques modifications

    encore une fois, un modèle ne sera jamais aussi bon qu'une fonctionnalité à part entière, espérons

    tout le monde comprend, sans aucun doute

    un modèle peut résoudre vos problèmes aujourd'hui alors qu'une caractéristique peut ou peut

    ne pas être près

    si une situation peut être résolue efficacement (jusqu'à 95%) en employant AB
    et C pourquoi quelqu'un aurait-il besoin que D fasse de même ? envie de sucre ? pourquoi serait
    nous nous concentrons sur quelque chose qui ne peut pas être résolu par un modèle ?

de toute façon, il y a des résolveurs de problèmes et des perfectionnistes, comme cela a été montré le
le problème est résoluble

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-126082053
.

Et pourrions-nous au moins avoir une idée de ce à quoi ressemblerait la structure de l'abri à vélos avant de nous demander de quelle couleur la peindre ? La plupart de ce que j'ai vu jusqu'à présent après les 10 premiers commentaires est "Hé, nous voulons construire un abri à vélos ! De quelle couleur devrions-nous le peindre ?" Rien à propos de s'assurer que la conception est structurellement solide. (Voir #3192 et la première moitié de #1206 pour quelques autres exemples de ceci - la plupart du bruit s'est calmé lorsque j'ai finalement fait une proposition sérieuse avec une syntaxe créée logiquement et une sémantique entièrement spécifiée.)

Gardez à l'esprit : cela entraînera très probablement une refactorisation majeure et une réécriture partielle des définitions de type de bibliothèque standard. Il en résultera également que la majorité des définitions de type sur DefinitelyTyped devront être réécrites pour la première version de TS qui prend en charge cela. Alors gardez à l'esprit que cela va certainement casser. (Une solution pourrait être de toujours émettre par défaut, même lorsque des erreurs liées à la valeur null se produisent, mais de fournir un indicateur à la --noImplicitAny pour modifier ce comportement.)

Certes, je suis un peu au-dessus de ma tête ici. Pourtant, j'ai recherché sur Scholar.google "nullable javascript" et "nullability javascript":
Comprendre TypeScript
a une belle table de types en TypeScript (module génériques).

Types dépendants pour JavaScript
semble être important. le code source a disparu

Faites confiance, mais vérifiez : dactylographie en deux phases pour dynamiqueLangues
Types de raffinement pour les langages de script
offre une solution basée sur le tapuscrit.
("Le raccourci t? représente t + null." ; sonne comme #186)

@afrische Une grande partie de cela est déjà utilisée pratiquement dans les vérificateurs de type JS. Flow utilise par exemple la plupart des expressions idiomatiques de « Trust, but Verify ». Il existe également Infernu , un vérificateur de type WIP JS qui s'appuie largement sur l'inférence de type Hindley-Milner 1 pour déduire ses types. Mais je m'égare...

[1] Haskell, OCaml, etc. utilisent également une version modifiée pour leurs systèmes de types.

Je joue actuellement avec un fork TS qui suppose que les types ne sont pas nullables par défaut et qui rend le type Null (existant) référençable avec null , par exemple string | null . J'ai également ajouté une syntaxe pour string? qui n'est qu'un raccourci pour le même type d'union.

Quand vous y réfléchissez bien, c'est la même chose que @aleksey-bykov, mais où null est le type Nothing , intégré.

Et ce n'est même pas compliqué à faire car le système de type est déjà assez bon pour gérer tout ça !

Là où les choses se compliquent, c'est que nous voulons avoir un chemin de transition en douceur. Nous avons besoin d'une certaine compatibilité ascendante, _au moins_ avec les fichiers de définition existants (la compatibilité avec le projet lui-même pourrait être un indicateur d'opt-in, bien qu'il serait préférable que la signification d'un morceau de code - disons sur Internet - ne t dépendent des indicateurs globaux du compilateur).

L'idée de @afrische d'utiliser string? dans votre propre projet mais d'utiliser string! dans le .d.ts pourrait être une nouvelle approche. Même si je n'aime pas trop la dualité arbitraire que cela crée. Pourquoi string nullable et certains fichiers et non nullable dans d'autres ? Cela semble bizarre.

Si vous préférez ne pas avoir de chaîne nullable dans certains fichiers et pas dans d'autres.
J'aime l'idée de @impinball d'avoir un indicateur de désinscription dans le compilateur et de l'introduire comme un changement décisif (halètement : grand changement par rapport à mes arguments précédents). En supposant que la nouvelle syntaxe ne provoquera pas d'erreurs lors de l'utilisation de l'indicateur de désinscription bien sûr.

Ce fil de discussion a plus d'un an et il est difficile de déterminer quelles sont toutes les conceptions et préoccupations pertinentes avec près de 300 commentaires et seulement quelques-uns de l'équipe TS elle-même.

Je prévois de migrer une grande base de code vers Flow, Closure Compiler ou TypeScript, mais le manque de sécurité nulle dans TypeScript est un véritable obstacle.

Sans avoir lu tout le fil, je ne peux pas vraiment comprendre ce qui ne va pas avec l'ajout des spécificateurs de nullabilité ! et ? tout en maintenant le comportement existant pour les types qui en manquent, alors voici une proposition :

Proposition

declare var foo:string // nullability unspecified
declare var foo:?string // nullable
declare var foo:!string // non-nullable

Avec ?string un sur-ensemble de !string , et string fois un sur-ensemble et un sous-ensemble des deux, c'est-à-dire la relation entre string et les deux ?string et !string est la même que la relation entre any et tous les autres types, c'est-à-dire qu'un type nu sans ! ou ? est comme un any par rapport à la nullité.

Les règles suivantes s'appliquent :

| Type | Contient | Fournit | Peut attribuer null ? | Peut supposer que non null ? |
| --- | --- | --- | --- | --- |
| T | T , !T , ?T | T , ?T , !T | oui | oui |
| ?T | T , !T , ?T | T , ?T | oui | non (type de protection requis) |
| !T | T , !T | T , ?T , !T | non | oui |

Cela fournit une sécurité nulle sans casser le code existant et permet aux bases de code existantes d'introduire la sécurité nulle de manière incrémentielle, tout comme any permet aux bases de code existantes d'introduire la sécurité de type de manière incrémentielle.

Exemple

Voici du code avec une erreur de référence nulle :

function test(foo:Foo) { foo.method(); }
test(null);

Ce code passe toujours. null est toujours attribuable à Foo et Foo peut toujours être considéré comme non nul.

function test(foo:!Foo) { foo.method(); }
test(null);

Maintenant, le test(null) est erroné, car null n'est pas assignable à !Foo .

function test(foo:?Foo) { foo.method(); }
test(null);

Maintenant, le foo.bar() est en erreur, car un appel de méthode sur ?Foo est interdit (c'est-à-dire que le code doit vérifier null ).

Merci! Et j'aime beaucoup cette idée. Bien que, pour des raisons de compatibilité,
pourrions-nous faire ?T et T juste des alias, comme Array<T> et T[] ? Ce
simplifierait les choses, et la version non décorée est toujours techniquement
nullable.

Le vendredi 28 août 2015, 07:14 Jesse Schalken [email protected] a écrit :

Ce fil a plus d'un an et il est difficile de comprendre ce que tous les
les conceptions et les préoccupations pertinentes sont avec près de 300 commentaires et seulement quelques
de l'équipe TS eux-mêmes.

Je prévois de migrer une grande base de code vers Flow, Closure Compiler ou
TypeScript mais le manque de sécurité de type dans TypeScript est un véritable briseur d'affaire.

Sans avoir lu tout le fil, je n'arrive pas à comprendre ce qui ne va pas
en ajoutant les deux ! et ? spécificateurs de nullabilité tout en maintenant le
comportement existant pour les types qui en manquent, alors voici une proposition :
Proposition

declare var foo:string // nullability unspecifieddeclare var foo:?string // nullable,declare var foo:!string // non nullable

Avec ?string un sur-ensemble de !string, et chaîne à la fois un sur-ensemble et un sous-ensemble
des deux, c'est-à-dire la relation entre la chaîne et à la fois ?chaîne et !chaîne
est la même que la relation entre n'importe quel type et tous les autres types, c'est-à-dire un
type nu sans ! ou ? est comme un tout en ce qui concerne la nullité.

Les règles suivantes s'appliquent :
Type Contient Fournit Peut affecter null ? Peut supposer non nul? TT, !T, ?TT,
?T, !T oui oui ?TT, !T, ?TT, ?T oui non (protection de type requise) !TT, !TT,
?T, !T non oui

Cela fournit une sécurité nulle sans casser le code existant et permet
bases de code existantes pour introduire la sécurité nulle de manière incrémentielle, comme n'importe quel
permet aux bases de code existantes d'introduire progressivement la sécurité des types.
Exemple

Voici du code avec une erreur de référence nulle :

test de fonction(foo:Foo) { foo.method(); }test(null);

Ce code passe toujours. null est toujours attribuable à Foo et Foo peut toujours
être supposé non nul.

test de fonction(foo:!Toto) { foo.method(); }test(null);

Maintenant, le test(null) est en erreur, puisque null n'est pas assignable à !Foo.

test de fonction(foo:?Toto) { foo.method(); }test(null);

Maintenant, foo.bar() est en erreur, car un appel de méthode sur ?Foo est interdit
(c'est-à-dire que le code doit vérifier la valeur null).

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-135743011
.

@impinball

Merci! Et j'aime beaucoup cette idée. Bien que, pour des raisons de compatibilité, pourrions-nous faire ?T et T juste des alias, comme Array<T> et T[] ? Cela simplifierait les choses, et la version non décorée est toujours techniquement annulable.

L'_idée globale_ est que T , ?T et !T sont trois choses distinctes, et que _est_ pour des raisons de compatibilité (compatibilité avec le code TS existant). Je ne sais pas comment l'expliquer mieux, désolé. Je veux dire, j'ai fait une petite table et tout.

D'accord. Bon point. J'ai mal interprété le tableau sur cette partie, et j'ai négligé
le cas du basculement des fichiers de définition existants, causant des problèmes avec
d'autres applications et bibliothèques.

Le vendredi 28 août 2015, à 11 h 43, Jesse Schalken [email protected] a écrit :

@impinball https://github.com/impinball

Merci! Et j'aime beaucoup cette idée. Bien que, pour des raisons de compatibilité,
pourrions-nous faire ?T et T juste des alias, comme Arrayet T[]? ce serait
simplifier les choses, et la version non décorée est toujours techniquement nullable.

L'idée entière est que T, ?T et !T sont trois choses distinctes,
et que _is_ pour des raisons de compatibilité (compatibilité avec les TS existants
code). Je ne sais pas comment l'expliquer mieux, désolé. Je veux dire, j'ai fait un
petite table et tout.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-135810311
.

désolé d'être un con, mais cela n'a pas de sens

?string un sur-ensemble de !string , et string fois un sur-ensemble et un sous-ensemble des deux

de la théorie des ensembles, nous savons qu'un ensemble A est un sous-ensemble et un sur-ensemble d'un ensemble B est quand et seulement quand A = B

si c'est le cas, quand vous dites "des deux", cela ne peut signifier que tout-de-?chaîne = tout-de-chaîne et tout-de-!chaîne = tout-de-chaîne

donc finalement tout-de-?chaîne = tout-de-!chaîne

d'où tout le monde descend, le bus ne va nulle part, le bus est hors service

@aleksey-bykov Probablement un cas de mauvaise formulation. Je pense qu'il veut dire que string ressemble plus à ?string | !string , en prenant les contraintes les plus permissives.

Tout cela a une portée similaire aux annotations de type @Nullable et @NonNull / @NotNull de Java de plus en plus courantes, et à la façon dont elles fonctionnent avec les types non annotés.

Et je serais certainement en faveur d'un nouveau drapeau pour les types implicitement considérés comme non nullables, en particulier les primitifs.

Un indicateur "non nullable par défaut" serait bien, mais il briserait également un grand nombre de définitions DefinitelyTyped. Cela inclut les définitions d'Angular et de Node, et cela nécessiterait beaucoup de travail fastidieux pour les corriger. Cela peut également nécessiter un rétroportage afin que les nouveaux types ne soient toujours pas analysés comme des erreurs de syntaxe, mais la possibilité de nullité n'est pas vérifiée. Un tel rétroportage serait le seul moyen pratique d'atténuer les cassures avec un tel indicateur, d'autant plus que les définitions sont mises à jour pour les types nullables. (Les gens utilisent encore TypeScript 1.4 en développement, en particulier dans les projets plus importants.)

@impinball

Les gens utilisent encore TypeScript 1.4 en développement, en particulier dans les projets plus importants.

Je pense que l'histoire de la compatibilité pour cette fonctionnalité est déjà assez difficile sans essayer de rendre les nouvelles définitions compatibles avec les anciens compilateurs (les types non nuls FWIW sont presque triviaux à ajouter au compilateur TS actuel).

Si les gens restent sur l'ancien TS, ils devraient utiliser les anciennes définitions. (je sais que ce n'est pas pratique).
Je veux dire que les choses vont casser de toute façon. Bientôt, TS 1.6 ajoutera des types d'intersection et les définitions qui l'utilisent ne seront pas compatibles non plus.

BTW, je suis surpris que les gens restent sur 1.4, il y a tellement de choses à aimer dans 1.5.

@aleksey-bykov

désolé d'être un con, mais cela n'a pas de sens

Je suppose que vous vous appelez un « intelligent » parce que vous savez parfaitement, d'après le reste de l'article, ce que « super-ensemble et sous-ensemble des deux » sont censés signifier. Bien sûr, cela n'a pas de sens s'il est appliqué à des ensembles réels.

any peut être assigné à n'importe quel type (il se comporte comme un sur-ensemble de tous les types) et traité comme n'importe quel type (il se comporte comme un sous-ensemble de tous les types). any signifie "se désinscrire de la vérification de type pour cette valeur".

string peut être attribué à la fois à partir de !string ou de ?string (il se comporte comme un sur-ensemble d'entre eux) et peut être traité à la fois comme !string et ?string (il se comporte comme un sous-ensemble d'entre eux). string signifie "se désinscrire de la vérification de nullité pour cette valeur", c'est-à-dire le comportement actuel du TS.

@impinball

Et je serais certainement en faveur d'un nouveau drapeau pour les types implicitement considérés comme non nullables, en particulier les primitifs.

@RyanCavanaugh a explicitement déclaré :

Les drapeaux qui modifient la sémantique d'une langue sont une chose dangereuse. [...] Il est important que quelqu'un qui regarde un morceau de code puisse "suivre" le système de types et comprendre les inférences qui sont faites. Si nous commençons à avoir un tas de drapeaux qui changent les règles de la langue, cela devient impossible.

La seule chose sûre à faire est de garder la même sémantique d'assignabilité et de changer ce qui est une erreur par rapport à ce qui ne dépend pas d'un indicateur, un peu comme la façon dont noImplicitAny fonctionne aujourd'hui.

C'est pourquoi ma proposition maintient le comportement existant pour les types sans spécificateur de ! NULL ( ? ). L'indicateur de sécurité _only_ à ajouter serait celui qui interdit les types dépourvus de spécificateur de nullabilité, c'est-à-dire qu'il ne prend que le code de passage et qu'il provoque une erreur, il ne change pas sa signification. Je suppose que noImplicitAny été autorisé pour la même raison.

@jesseschalken

Je voulais dire que dans le contexte de variables typées implicitement non initialisées
à null ou undefined dans des cas comme ceux-ci :

var a = new Type(); // type: !Type
var b = 2; // type: !number
var c = 'string'; // type: !string
// etc...

Désolé pour la confusion.

Le vendredi 28 août 2015, à 19 h 54, Jesse Schalken [email protected] a écrit :

@impinball https://github.com/impinball

Et je serais certainement en faveur d'un nouveau drapeau pour les types étant
implicitement pris comme non nullable, en particulier primitifs.

@RyanCavanaugh https://github.com/RyanCavanaugh a explicitement déclaré :

Les drapeaux qui modifient la sémantique d'une langue sont une chose dangereuse. [...]
Il est important que quelqu'un qui regarde un morceau de code puisse "suivre"
avec le système de types et comprendre les inférences qui sont faites. Si
nous commençons à avoir un tas de drapeaux qui changent les règles de la langue,
cela devient impossible.

La seule chose sûre à faire est de garder la sémantique de
l'assignabilité est la même et change ce qui est une erreur par rapport à ce qui ne dépend pas
sur un drapeau, un peu comme la façon dont noImplicitAny fonctionne aujourd'hui.

C'est pourquoi ma proposition maintient le comportement existant pour les types manquant
un spécificateur de valeur NULL (! ou ?). L'indicateur de sécurité _only_ à ajouter serait
un qui interdit les types sans spécificateur de nullabilité, c'est-à-dire qu'il
prend le code de passage et provoque une erreur, cela ne change pas sa signification. je
supposez que noImplicitAny a été autorisé pour la même raison.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-135916676
.

@jeffmcafer ce que vous essayez de dire en modifiant le sens original des mots surensemble et sous-ensemble peut toujours être articulé en termes d'ensembles facilement : l'ensemble de valeurs de string est une union de valeurs de !string et ?string signifiant n'importe quoi de !string ou/et ?string appartient à string

@impinball Encore une fois, un tel indicateur changerait la signification du code existant, ce qui (à la lecture des commentaires de @RyanCavanaugh ) n'est pas autorisé. export var b = 5; exportera maintenant un !number là où il exportait auparavant un number .

Oui, dans un sens. Pour être un peu plus technique, il accepte l'union, mais
il fournit l'intersection. Fondamentalement, l'un ou l'autre type peut compter comme un
string , et un string peut être passé pour l'un ou l'autre type.

Le vendredi 28 août 2015, 20h20, Aleksey Bykov [email protected] a écrit :

@impinball https://github.com/impinball ce que vous essayez de dire par
modifier le sens original des mots surensemble et sous-ensemble peut toujours être
articulé en termes d'ensembles facilement : l'ensemble de valeurs de chaîne est un
_union_ des valeurs de !string et ?string signifiant quelque chose de !string
ou/et ?string appartient à string

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-135920233
.

De toute évidence, il n'est pas censé être activé par défaut. Et la plupart des fichiers de définition
ne serait pas affecté. Techniquement, tout change qui n'est pas purement
additif (même l'ajout d'un nouvel argument à une fonction n'est pas en JavaScript) a
la capacité de casser les applications. Sa portée est similaire à
noImplicitAny en ce sens qu'il force une saisie un peu plus explicite. Et moi
ne croyez pas qu'il puisse casser beaucoup plus que cela, d'autant plus que le seul
la façon dont cela pourrait affecter d'autres fichiers est via les exportations de TypeScript réel
fichiers source. (L'autre indicateur a cassé de nombreux fichiers de définition et désactivé
un moyen fréquent de tester les canards.)

Le vendredi 28 août 2015, à 20 h 21, Jesse Schalken [email protected] a écrit :

@impinball https://github.com/impinball Encore une fois, un tel drapeau changerait
la signification du code existant, qui (en lisant @RyanCavanaugh
https://github.com/Commentaires de RyanCavanaugh) n'est pas autorisé. var d'exportation
b = 5 ; va maintenant exporter un !numéro là où il exportait auparavant un numéro.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-135920315
.

De toute évidence, il n'est pas censé être activé par défaut.

Cela change encore le sens du code. Dès que vous faites cela, vous ne pouvez pas lire du code TypeScript et dire "Je sais ce que cela signifie", ni écrire une bibliothèque que quelqu'un d'autre utilisera, car la signification du code dépend des indicateurs utilisés pour compiler, et c'est inacceptable. Vous avez littéralement coupé la langue en deux.

C'est pourquoi ma proposition maintient le comportement existant pour les types sans spécificateur de ? NULL ( ! ).

@jesseschalken
Si vous voulez (et parvenez) conserver avec une fidélité à 100% le sens du code existant, alors vous devriez abandonner l'idée de la sécurité nulle sur le tapuscrit. Aucune solution ne sera utilisable en pratique.

Devoir mettre ? et ! sur chaque annotation de type est déjà assez verbeux pour que je ne veuille pas le faire, mais vous devriez également abandonner toute inférence de type. let x = 3 déduit number aujourd'hui. Si vous n'acceptez pas un changement ici, cela signifie que vous devez tout taper explicitement pour profiter des avantages de la sécurité nulle. Pas quelque chose que je suis prêt à faire non plus.

Lorsque les avantages l'emportent sur les inconvénients, certaines concessions peuvent être faites. Comme l'a souligné @impinball, l'équipe TS a fait exactement cela avec noImplicitAny . C'est un indicateur qui crée de nouvelles erreurs dans votre code lorsque vous l'activez. En tant que tel, copier-coller du code à partir d'Internet, ou même simplement utiliser les bibliothèques TS, peut se briser si le code que vous saisissez n'a pas été écrit sous l'hypothèse noImplicitAny (et cela m'est arrivé).

Je pense que la sécurité nulle peut être introduite de la même manière. Le sens du code est le même et il s'exécute avec une sémantique exactement identique. Mais sous un nouveau drapeau (disons noImplicitNull ou autre), vous obtiendrez des vérifications supplémentaires et des avertissements/erreurs du code qui n'est pas écrit avec l'hypothèse noImplicitNull .

Je pense que la sécurité nulle peut être introduite de la même manière. Le sens du code est le même
et il fonctionne avec une sémantique exactement identique. Mais sous un nouveau drapeau (dites noImplicitNull ou autre)
vous obtiendrez des vérifications supplémentaires et des avertissements/erreurs du code qui n'est pas écrit avec
l'hypothèse noImplicitNull.

J'aime cette approche et cela semble une manière logique de faire évoluer la langue. J'espère qu'avec le temps, cela deviendra la norme de facto, de la même manière que les frappes sont généralement écrites avec noImplicitAny à l'esprit.

Cependant, je pense que l'important en ce qui concerne l'adoption progressive est de s'assurer que le code existant peut être migré module par module et que le nouveau code écrit avec des valeurs nulles explicites à l'esprit peut facilement fonctionner avec du code existant qui utilise des valeurs nulles implicites.

Alors que diriez-vous de ceci:

  • Avec -noImplicitNull, T devient un alias pour !T . Sinon, la nullité de T est inconnue.
  • L'indicateur doit être modifiable module par module en ajoutant une annotation en haut, par exemple. <strong i="18">@ts</strong>:implicit_null . Ceci est similaire à la manière dont Flow permet la vérification de type module par module.
  • La conversion d'une base de code existante se fait en ajoutant d'abord l'option de compilateur -noImplicitNull, puis en annotant tous les modules existants avec l' indicateur '
  • Lors de l'importation d'un module, il doit y avoir une politique sur la façon de convertir les types si les modules importés et les modules d'importation ont des paramètres d'implicite différents.

Il existe différentes options pour ce dernier point si un module avec des valeurs NULL explicites en importe un avec des valeurs NULL implicites.

  • Une approche extrême consisterait à traiter la nullité des types T comme inconnues et à exiger que l'importateur transtype explicitement le type en ?T ou !T . Cela nécessiterait beaucoup d'annotations dans l'appelé mais serait safar.
  • Une autre approche serait de traiter tous les types T importés comme ?T . Cela nécessiterait également beaucoup d'annotations dans l'appelant.
  • Enfin, tous les types T importés pourraient être traités comme !T . Ce serait bien sûr faux dans certains cas, mais cela pourrait être l'option la plus pragmatique. Similaire à la façon dont une variable de type any peut être affectée à une valeur de type T .

Les pensées?

@jods4 Le noImplicitAny _ne change pas_ la signification du code existant, il exige seulement que le code soit explicite sur quelque chose qui serait autrement implicite.

| Code | Drapeau | Signification |
| --- | --- | --- |
| interface Foo { blah; } | | interface Foo { blah:any; } |
| interface Foo { blah; } | noImplicitAny | erreur, type explicite requis |
| var foo = 'blah' | | var foo:string = 'blah' |
| var foo = 'blah' | noImplicitNull | var foo:!string = 'blah' |

Avec noImplicitNull , avant vous aviez une variable dans laquelle null pouvait être écrit. Vous avez maintenant une variable dans laquelle null _ne peut pas_ être écrit. C'est une bête complètement différente de noImplicitAny .

@RyanCavanaugh a déjà exclu les indicateurs qui modifient la sémantique du code existant. Si vous allez carrément ignorer les exigences expresses de l'équipe TS, alors ce ticket va traîner pendant une autre année.

@jesseschalken Désolé mais je ne vois pas la différence.
Avant noImplicitAny vous pouvez avoir ceci dans votre code :
let double = x => x*2;
Il compile et fonctionne très bien. Mais une fois que vous avez activé noImplicitAny , le compilateur vous renvoie une erreur en disant que x est implicitement n'importe lequel. Vous devez modifier votre code pour le faire fonctionner avec le nouveau drapeau :
let double = (x: any) => x*2 ou mieux encore let double = (x: number) => x*2 .
Notez que bien que le compilateur ait généré une erreur, il émettrait toujours du code JS parfaitement fonctionnel (à moins que vous ne désactiviez l'émission en cas d'erreur).

La situation avec les nulls est à peu près la même à mon avis. Supposons pour la discussion qu'avec le nouveau drapeau, T n'est pas null et T? ou T | null dénote l'union de type T et null .
Avant vous aviez peut-être :
let foo: string; foo = null; ou même juste let foo = "X"; foo = null qui serait inféré à string tout de même.
Il compile et fonctionne très bien. Activez maintenant le nouveau drapeau noImplicitNull . Soudain, TS renvoie une erreur indiquant que vous ne pouvez pas affecter null à quelque chose qui n'a pas été explicitement déclaré comme tel. Mais à l'exception de l'erreur de frappe, votre code émet toujours _le même_, un code JS correct.
Avec le drapeau, vous devez déclarer explicitement votre intention et modifier le code :
string? foo; foo = null;

Alors, quelle est la différence, vraiment ? Le code émet toujours correctement et son comportement d'exécution n'a pas du tout changé. Dans les deux cas, vous obtenez des erreurs du système de typage et vous devez modifier votre code pour qu'il soit plus explicite dans vos déclarations de type pour vous en débarrasser.

De plus, dans les deux cas, il est possible de prendre du code écrit sous le drapeau strict et de le compiler avec le drapeau désactivé et cela fonctionne toujours de la même manière et sans erreur.

@robertknight Très proche de ma pensée actuelle.

Pour les modules/définitions qui n'ont pas opté pour les types non nuls stricts, T devrait fondamentalement signifier : désactiver toutes sortes d'erreurs nulles sur ce type. Essayer de le contraindre à T? peut toujours créer des problèmes de compatibilité.

Le problème est qu'aujourd'hui, certains T sont en fait non nullables et certains sont nullables. Envisager:

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

Si vous créez string alias string? dans le module hérité, l'appel devient une erreur car vous transmettez une variable éventuellement nulle dans un paramètre non nul.

@jods4 Relisez mon commentaire. Regarde la table. Je ne sais plus comment l'exprimer plus clairement.

Votre exemple a été explicitement conçu pour arriver à votre conclusion en plaçant la définition de foo et l'affectation de foo l'une à côté de l'autre. Avec noImplicitAny , les seules erreurs qui en résultent proviennent spécifiquement du code qui doit changer (car il n'a pas changé de sens, il a seulement demandé qu'il soit exprimé plus explicitement). Avec noImplicitNull , le code qui a causé l'erreur était l'_affectation_ à foo mais le code qui devait changer pour le corriger (pour avoir l'ancien sens) était la _définition_ de foo . C'est extrêmement important, car le drapeau a _modifié la signification de la définition de foo _. L'affectation et la définition peuvent évidemment se trouver sur des côtés différents d'une limite de bibliothèque, pour laquelle le drapeau noImplicitNull a changé la _signification_ de l'interface publique de cette bibliothèque !

le drapeau a changé le sens de la définition de foo.

Oui c'est vrai. Il est passé de "Je n'ai pas la moindre idée si la variable peut contenir null ou non - et je m'en fiche" en "Cette variable est sans null". Il y a 50/50 chances que ce soit juste et si ce n'est pas le cas, vous devez préciser votre intention dans la déclaration. Au final, le résultat est le même qu'avec noImplicitAny : vous devez rendre votre intention plus explicite dans la déclaration.

L'affectation et la définition peuvent évidemment être de différents côtés d'une limite de bibliothèque

En effet, typiquement la déclaration dans la librairie et l'utilisation dans mon code. À présent:

  1. Si la bibliothèque a opté pour des valeurs nulles strictes, elle doit déclarer ses types correctement. Si la bibliothèque dit qu'elle a des types null stricts et que x n'est pas nullable, alors j'essaie d'attribuer un null _est en effet une erreur qui devrait être signalée_.
  2. Si la bibliothèque n'a pas opté pour les valeurs nulles strictes, le compilateur ne devrait générer aucune erreur pour son utilisation.

Ce drapeau (tout comme noImplicitAny ) ne peut pas être activé sans ajuster votre code.

Je vois votre point, mais je dirais que nous ne _changeons_ pas le code de sens ; nous _exprimons plutôt une signification qui n'est pas classée_ par le système de types aujourd'hui.

Non seulement c'est bien car cela détectera les erreurs dans le code d'aujourd'hui, mais je dirais que sans prendre une telle mesure, il n'y aura jamais de types non nuls utilisables dans TS.

Bonne nouvelle pour les types non nullables ! Il semble que l'équipe TS soit d'accord pour introduire des changements de rupture dans les mises à jour TS !
Si vous voyez ceci :
http://blogs.msdn.com/b/typescript/archive/2015/09/02/announcing-typescript-1-6-beta-react-jsx-better-error-checking-and-more.aspx
Ils introduisent un changement de rupture (syntaxe facultative mutuellement exclusive) avec un nouveau type de fichier, et ils introduisent un changement de rupture _sans_ le nouveau type de fichier (affecte tout le monde).
C'est un précédent avec lequel nous pouvons discuter des types non nullables (par exemple, un .sts ou une extension Typescript stricte et le .sdts correspondant).

Maintenant, nous devons juste déterminer si nous voulons que le compilateur essaie de vérifier les types non définis ou simplement les types nuls (et quelle syntaxe) et nous avons une proposition solide.

@jbondc Lecture très intéressante. Heureux de voir que mon intuition sur la migration vers NNBD (non nullable par défaut) étant plus facile que la migration vers option non nullable a été confirmée par des études (un ordre de grandeur moins de changements à migrer, et dans le cas de Dart, 1- 2 annotations pour 1 000 lignes de code nécessitaient des modifications de nullité, pas plus de 10, même dans le code lourd)

Je ne sais pas si la complexité du document reflète vraiment la complexité des types non nullables. Par exemple, dans la section générique, ils discutent de 3 types de paramètres de type formel, puis montrent que vous n'en avez pas réellement besoin. Dans TS, null serait simplement le type de l'enregistrement complètement vide (pas de propriétés, pas de méthodes) tandis que {} serait le type racine non nul, puis les génériques non nuls sont simplement G<T extends {}> - pas besoin de discuter de plusieurs types de paramètres de type formel.

De plus, il semble qu'ils proposent beaucoup de sucre non essentiel, comme var !x

L'enquête sur les langues existantes qui ont traité le même problème est cependant le vrai bijou.

En lisant le document, j'ai réalisé que les types Optional / Maybe sont plus puissants que les types nullables, en particulier dans un contexte générique - principalement en raison de la possibilité d'encoder Just(Nothing) . Par exemple, si nous avons une interface Map générique qui contient des valeurs de type T et supporte get qui peut renvoyer ou non une valeur selon la présence d'une clé :

interface Map<T> {
  get(s:string):Maybe<T>
}

rien n'empêche T d'être de type Maybe<U> ; le code fonctionnera parfaitement et renverra Just(Nothing) si une clé est présente mais contient une Nothing , et renverra simplement Nothing si la clé n'est pas présente du tout.

En revanche, si nous utilisons des types nullables

interface Map<T> {
  get(s:string):T?
}

alors il est impossible de faire la distinction entre une clé manquante et une valeur nulle lorsque T est nullable.

Dans tous les cas, la possibilité de différencier les valeurs nullables des valeurs non nullables et de modéliser les méthodes et propriétés disponibles en conséquence est une condition préalable à tout type de sécurité de type.

@jbondc C'est une trouvaille très

Je trouve réconfortant que des études montrent que 80 % des déclarations sont en fait non nulles ou qu'il n'y a que 20 annotations de nullité par KLOC (p. 21). Comme indiqué dans le document, il s'agit d'un argument de poids pour non-null par défaut, ce qui était également mon sentiment.

Un autre argument en faveur de non-null est qu'il crée un système de type plus propre : null est son propre type, T est non-null et T? est un synonyme de T | null . Parce que TS a déjà un type d'union, tout est beau, propre et fonctionne bien.

En voyant la liste des langages récents qui ont résolu ce problème, je pense vraiment qu'un nouveau langage de programmation moderne devrait gérer ce problème de longue date et réduire les bogues nuls dans les bases de code. C'est un problème beaucoup trop courant pour quelque chose qui devrait être modélisé dans le système de types. J'espère toujours que TS l'aura un jour.

J'ai trouvé l'idée de l'opérateur T! intrigante et peut-être utile. Je pensais à un système où T est un type non nul, T? est T | null . Mais cela m'a dérangé que vous ne puissiez pas vraiment créer une API générique qui garantisse un résultat non nul même face à une entrée nulle. Je n'ai pas de bons cas d'utilisation, mais en théorie, je ne pourrais pas modéliser cela fidèlement : function defined(x) { return x || false; } .
En utilisant l'opérateur d'inversion non nul, on pourrait faire : function defined<T>(x: T): T! | boolean . Cela signifie que si defined renvoie un T il est garanti qu'il n'est pas nul, même si la contrainte générique T était nulle, disons string? . Et je ne pense pas que ce soit difficile à modéliser dans TS : étant donné T! , si T est un type d'union qui inclut le type null , renvoie le type résultant de la suppression de null du syndicat.

@spion

En lisant le document, j'ai réalisé que les types optionnels / peut-être sont plus puissants que les types nullables

Vous pouvez imbriquer des structures Maybe , vous ne pouvez pas imbriquer null , en effet.

C'est une discussion intéressante dans le contexte de la définition de nouvelles API, mais lors de la cartographie des API existantes, il y a peu de choix. Rendre la correspondance linguistique Nulls à Maybe ne profitera pas de cet avantage, à moins que la fonction ne soit entièrement réécrite.

Maybe encode deux informations distinctes : s'il existe une valeur et quelle est la valeur. En prenant votre exemple de Map et en regardant C#, c'est évident, à partir de Dictionary<T,K> :
bool TryGet(K key, out T value) .
Notez que si C# avait des tuples (peut-être C# 7), c'est fondamentalement la même chose que :
(bool hasKey, T value) TryGet(K key)
Ce qui est essentiellement un Maybe et permet de stocker null .

Notez que JS a sa propre façon de traiter ce problème et cela crée tout un tas de nouveaux problèmes intéressants : undefined . Un JS typique Map renverrait undefined si la clé n'est pas trouvée, sa valeur sinon, y compris null .

Proposition connexe pour C# 7 - https://github.com/dotnet/roslyn/issues/5032

Vous réalisez que le problème n'est pas résolu à moins que vous ne modélisiez undefined de la même manière ?
Sinon, tous vos problèmes nuls seront simplement remplacés par des problèmes non définis (qui, imo, sont de toute façon plus répandus).

@Griffork

tous vos problèmes nuls seront simplement remplacés par des problèmes non définis

Non, pourquoi le feraient-ils ?
Mes problèmes nuls disparaîtront et mes problèmes indéfinis resteront.

Certes, undefined est toujours un problème. Mais cela dépend beaucoup de votre style de codage. Je code avec presque pas de undefined sauf là où le navigateur me les force, ce qui signifie que 90% de mon code serait plus sûr avec des contrôles nuls.

Je code presque sans indéfini sauf là où le navigateur me l'impose

J'aurais pensé que JavaScript force undefined sur un à chaque tour.

  • Variables non initialisées. let x; alert(x);
  • Arguments de fonction omis. let foo = (a?) => alert(a); foo();
  • Accéder à des éléments de tableau inexistants. let x = []; alert(x[0]);
  • Accéder à des propriétés d'objet inexistantes. let x = {}; alert(x['foo']);

Null, en revanche, se produit dans des situations moins nombreuses et plus prévisibles :

  • Accès DOM. alert(document.getElementById('nonExistent'));
  • Réponses de services Web tiers (puisque JSON.stringify supprime undefined ) . { name: "Joe", address: null }
  • Regex.

Pour cette raison, nous interdisons l'utilisation de null , convertissons tous les null reçus via le câble en undefined , et utilisons toujours une vérification d'égalité stricte pour undefined . Cela a bien fonctionné pour nous dans la pratique.

Par conséquent, je suis d'accord pour dire que le problème undefined est le plus répandu.

Style de codage de

Variables non initialisées

J'initialise toujours les variables et j'ai noImplicitAny activé, donc let x serait de toute façon une erreur. Le plus proche que j'utiliserais dans mon projet est let x: any = null , bien que ce soit du code que je n'écrirais pas souvent.

Paramètres de fonction optionnels

J'utilise les valeurs de paramètre par défaut pour les paramètres facultatifs, il me semble que cela a plus de sens (votre code _lira_ et utilisera le paramètre d'une manière ou d'une autre, n'est-ce pas ?). Donc pour moi : function f(name?: string = null, options?: any = {}) .
Accéder à la undefined brute du paramètre

Accéder à des éléments de tableau inexistants

C'est quelque chose que je m'efforce de ne pas faire dans mon code. Je vérifie la longueur de mes tableaux pour ne pas sortir des limites et je n'utilise pas de tableaux clairsemés (ou j'essaie de remplir les emplacements vides avec des valeurs par défaut telles que null , 0 , ...).
Encore une fois, vous pouvez proposer un cas particulier où je ferais cela, mais ce serait une _exception_, pas la règle.

Accéder à des propriétés d'objet inexistantes.

A peu près la même chose que pour les tableaux. Mes objets sont tapés, si une valeur n'est pas disponible, je la mets à null , pas à undefined . Encore une fois, vous pouvez trouver des cas limites (comme faire une sonde de dictionnaire), mais ce sont des cas limites et non représentatifs de mon style de codage.

Dans tous les cas _exceptionnels_ où je récupère un undefined , je prends immédiatement des mesures sur le résultat undefined et je ne le propage pas davantage ni ne "travaille" avec lui. Exemple typique du monde réel dans un compilateur TS fictif avec vérifications nulles :

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

Pour cette raison, nous interdisons l'utilisation de null , convertissons tous les null reçus sur le câble en undefined et utilisons toujours une vérification d'égalité stricte pour undefined . Cela a bien fonctionné pour nous dans la pratique.

D'après cela, je comprends que vous utilisez un style de codage très différent du mien. Si vous utilisez essentiellement undefined partout, alors oui, vous bénéficierez beaucoup moins des types null vérifiés statiquement que moi.
voudrais.

Oui, la chose n'est pas étanche à 100% à cause de undefined et je ne pense pas qu'on puisse créer un langage raisonnablement utilisable qui soit 100% correct à cet égard. JS introduit undefined à trop d'endroits.

Mais comme j'espère que vous pouvez le constater d'après mes réponses ci-dessus, avec un style de codage approprié, il y a _beaucoup_ pour bénéficier des contrôles nuls. Au moins, mon opinion est que dans ma base de code, cela aiderait à trouver et à prévenir de nombreux bogues stupides et améliorerait la productivité dans mon environnement d'équipe.

@jods4 , c'était intéressant de lire votre approche.

Je pense que l'objection que j'ai à cette approche est qu'il semble y avoir beaucoup de règles à respecter - c'est un peu comme le communisme contre le marché libre :sourire:

L'équipe TS a en interne une règle similaire à la nôtre pour son propre style.

@NoelAbrahams "Utilisez undefined " est autant une règle que "Utilisez null ".

Dans tous les cas, la cohérence est la clé et je n'aimerais pas un projet où je ne sais pas si les choses sont censées être null ou undefined (ou une chaîne vide ou zéro). D'autant plus que TS n'aide actuellement pas à résoudre ce problème...

Je sais que TS a une règle en faveur de undefined sur null , je suis curieux de savoir s'il s'agit d'un choix arbitraire "pour des raisons de cohérence" ou s'il y a plus d'arguments derrière le choix.

Pourquoi je préfère utiliser null plutôt que undefined :

  1. Cela fonctionne de manière familière pour nos développeurs, beaucoup proviennent de langages OO statiques tels que C#.
  2. Les variables non initialisées sont généralement considérées comme une odeur de code dans de nombreuses langues, je ne sais pas pourquoi cela devrait être différent dans JS. Expliquez clairement votre intention.
  3. Bien que JS soit un langage dynamique, les performances sont meilleures avec des types statiques qui ne changent pas. Il est plus efficace de définir une propriété sur null que sur delete .
  4. Il prend en charge la différence nette entre null qui est défini mais signifie l'absence de valeur, et undefined , qui est... indéfini. Un endroit où la différence entre les deux est flagrante : les paramètres optionnels. Ne pas passer un paramètre entraîne undefined . Comment _passez-vous_ la valeur vide si vous utilisez undefined pour cela dans votre base de code ? En utilisant null il n'y a pas de problème ici.
  5. Il ouvre un chemin clair vers la vérification nulle, comme indiqué dans ce fil de discussion, ce qui n'est pas vraiment pratique avec undefined . Bien que peut-être je rêvasse sur celui-ci.
  6. Vous devez faire un choix pour la cohérence, IMHO null est aussi bon que undefined .

Je pense que la raison de préférer undefined à null est à cause de
arguments par défaut et cohérence avec le retour de obj.nonexistentProp
undefined .

A part ça, je ne comprends pas le bikeshedding sur ce qui compte comme null
suffisant pour exiger que la variable soit nullable.

Le mardi 8 septembre 2015, 06:48 jods [email protected] a écrit :

@NoelAbrahams https://github.com/NoelAbrahams "Utiliser undefined" est comme
beaucoup une règle comme "Utiliser null".

Dans tous les cas, la cohérence est la clé et je ne voudrais pas un projet où je suis
pas sûr si les choses sont censées être nulles ou indéfinies (ou un vide
chaîne ou zéro). D'autant plus que TS n'aide actuellement pas avec cela
publier...

Je sais que TS a une règle pour favoriser undefined sur null, je suis curieux de savoir si cela
est un choix arbitraire « pour des raisons de cohérence » ou s'il y a plus
arguments derrière le choix.

Pourquoi _I_ aime utiliser null plutôt qu'undefined :

  1. Cela fonctionne d'une manière familière pour nos développeurs, beaucoup viennent de l'OO statique
    langages tels que C#.
  2. Les variables non initialisées sont généralement considérées comme une odeur de code dans
    de nombreuses langues, je ne sais pas pourquoi cela devrait être différent dans JS. Faire votre
    intention claire.
  3. Bien que JS soit un langage dynamique, les performances sont meilleures avec
    types statiques qui ne changent pas. Il est plus efficace de définir une propriété sur
    null que de le supprimer.
  4. Il prend en charge la différence nette entre null qui est défini mais
    signifie l'absence de valeur, et indéfini, qui est... indéfini.
    Un endroit où la différence entre les deux est flagrante : facultatif
    paramètres. Le fait de ne pas passer un paramètre a pour résultat undefined. Comment tu
    _passez_ la valeur vide si vous utilisez undefined pour cela dans votre code
    base? En utilisant null, il n'y a pas de problème ici.
  5. Il ouvre un chemin clair vers la vérification nulle, comme discuté dans ce
    thread, ce qui n'est pas vraiment pratique avec undefined. Bien que peut-être
    Je rêvasse sur celui-ci.
  6. Vous devez faire un choix pour la cohérence, IMHO null est aussi bon que
    indéfini.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-138514437
.

@impinball
Pouvons-nous arrêter d'utiliser « bikeshedding » dans chaque discussion github ? Nous pouvons dire en toute sécurité qu'utiliser undefined ou null est une préférence d'équipe ; mais la question de savoir s'il faut essayer d'inclure undefined dans les contrôles nuls (ou non) et comment cela fonctionnerait n'est pas triviale. Donc, je ne comprends pas comment c'est le bikeshedding en premier lieu?

J'ai construit un fork de TS 1.5 avec des types non nullables et c'était étonnamment facile. Mais je pense qu'il y a deux problèmes difficiles qui nécessitent un consensus pour avoir des types non nullables dans le compilateur TS officiel, les deux ont été longuement discutés ci-dessus sans conclusion claire :

  1. Que fait-on avec undefined ? (mon avis : c'est encore partout et non coché)
  2. Comment gérons-nous la compatibilité avec le code existant, en particulier les définitions ? (mon avis : indicateur d'activation, au moins par fichier de définition. L'activation de l'indicateur est « rupture » ​​car vous devrez peut-être ajouter des annotations nulles.)

Mon opinion personnelle est que nul et indéfini doivent être traités
de manière équivalente aux fins de nullité. Les deux sont utilisés pour ce cas d'utilisation,
représentant l'absence d'une valeur. L'un est que la valeur n'a jamais existé,
et l'autre est que la valeur existait autrefois et n'existe plus. Les deux
devrait compter pour la nullité. La plupart des fonctions JS retournent undefined, mais beaucoup
Les fonctions DOM et bibliothèque renvoient null. Les deux servent le même cas d'utilisation. Ainsi,
ils doivent être traités de manière équivalente.

La référence bikeshedding concernait les arguments de style de code sur lesquels
doit être utilisé pour représenter l'absence d'une valeur. Certains plaident pour
juste nul, certains plaident pour juste undefined, et certains plaident pour un
mélange. Cette proposition ne doit pas se limiter à une seule de celles-ci.

Le mardi 8 septembre 2015, à 13 h 28, jods [email protected] a écrit :

@impinball https://github.com/impinball
Pouvons-nous arrêter d'utiliser « bikeshedding » dans chaque discussion github ? Nous pouvons en toute sécurité
dire que l'utilisation d'undefined ou null est une préférence d'équipe ; mais le problème
s'il faut essayer d'inclure undefined dans les contrôles nuls (ou non) et comment
ça marcherait n'est pas anodin. Donc je ne comprends pas comment ça se passe dans le bikeshed
première place?

J'ai construit une fourchette de TS 1.5 avec des types non nullables et c'était
étonnamment facile. Mais je pense qu'il y a deux problèmes difficiles qui
besoin d'un consensus pour avoir des types non nullables dans le compilateur TS officiel,
les deux ont été longuement discutés ci-dessus sans conclusion claire :

  1. Que fait-on de l'indéfini ? (mon avis : c'est encore partout
    et non coché)
  2. Comment gérons-nous la compatibilité avec le code existant, en particulier
    définitions ? (mon avis : indicateur d'opt-in, au moins par fichier de définition.
    L'activation du drapeau est "rupture" car vous devrez peut-être ajouter null
    annotations.)

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -138641395
.

@ jods4 Je

Pouvez-vous le lier s'il vous plaît? Je voudrais l'essayer.

@impinball
J'aimerais voir (une certaine) sécurité contre undefined aussi, mais c'est assez répandu.

En particulier, pouvons-nous définir un tableau de types non nullables ?
Étant donné qu'un accès à un tableau hors limites (ou clairsemé) renvoie undefined , pouvons-nous facilement définir et utiliser des tableaux ?
Je pense qu'exiger que tous les tableaux soient nullables est un fardeau trop lourd dans la pratique.

Devrions-nous différencier les types null et undefined ? Ce n'est pas difficile : T | null , T | undefined , T | null | undefined et peut fournir une réponse facile à la question ci-dessus. Mais alors qu'en est-il de la syntaxe abrégée : que signifie T? ? À la fois nul et indéfini ? Avons-nous besoin de deux raccourcis différents?

@Arnavion
Les types Null et Undefined existent déjà dans TS.
Mon avis était de :

  1. Rendre tous les types non nullables (y compris les types inférés) ;
  2. Donnez au type null un nom ( null ) que vous pouvez utiliser dans les déclarations de type ;
  3. Supprimer l'élargissement de null à any ;
  4. Introduire la syntaxe abrégée T? qui est la même que T | null ;
  5. Supprimez les conversions implicites de null vers tout autre type.

Sans accès à mes sources, je pense que c'est l'essentiel. Le type Null existant et le merveilleux système de type TS (en particulier les types union et les protections de type) font le reste.

Je n'ai pas encore validé mon travail sur github, je ne peux donc pas partager de lien pour le moment. Je voulais d'abord coder le commutateur de compatibilité mais j'ai été très occupé avec d'autres choses :(
Le commutateur de compatibilité est beaucoup plus impliqué <_<. Mais c'est important car pour le moment, le compilateur TS se compile avec beaucoup d'erreurs et de nombreux tests existants échouent.
Mais cela semble fonctionner correctement sur un tout nouveau code.

Permettez-moi de résumer ce que j'ai vu de certains commentateurs jusqu'à présent dans l'espoir de pouvoir illustrer comment cette conversation tourne en rond :
Problème : certaines valeurs de « cas spéciaux » trouvent leur place dans un code qui n'est pas conçu pour les gérer, car elles sont traitées différemment de tous les autres types du langage (c'est-à-dire nuls et indéfinis).
Moi : Pourquoi n'établissez-vous pas simplement des normes de programmation pour que ces problèmes ne se produisent pas ?
Autre : parce que ce serait bien si l'intention pouvait être reflétée dans la saisie, car elle ne sera alors pas documentée différemment par chaque équipe travaillant dans le script de saisie, et moins de fausses hypothèses sur les bibliothèques tierces se produiront.
Everone : Comment allons-nous régler ce problème ?
Autre : rendons les valeurs nulles plus strictes et utilisons des normes pour gérer les indéfinis !

Je ne vois pas comment cela peut être considéré comme une solution alors que indéfini est bien plus un problème que null !

Avis de non-responsabilité : cela ne reflète pas les attitudes de tout le monde ici, c'est juste que c'est assez venu que je voulais souligner cela.
La seule façon dont cela sera accepté, c'est s'il s'agit d'une solution à un problème, pas une petite partie d'une solution à la plus petite partie d'un problème.

Putain de téléphone !
*toutes les personnes

*c'est assez venu pour que je veuille le souligner.

De plus, le dernier paragraphe devrait se lire « le seul moyen pour cette proposition »

@ jods4 Je dirais que cela dépendrait de certaines constructions. Dans certains cas, vous pouvez garantir la non-nullabilité dans ces cas, tels que les suivants :

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

Dans ce cas, le compilateur devrait avoir une tonne de logique pour s'assurer que le tableau est vérifié dans les limites :

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

Et dans ce cas, il n'y a aucun moyen de garantir que le compilateur puisse vérifier que l'accès pour obtenir entry est dans les limites sans réellement évaluer des parties du code :

declare const list: T![]

const end = round(max, list.length);

for (let i = 0; i < end; i++) {
  const entry: T![] = list[i];
}

Il y en a des faciles et évidents, mais il y en a d'autres plus difficiles.

@impinball En effet, les API modernes telles que map , forEach ou for..of sont correctes car elles ignorent les éléments qui n'ont jamais été initialisés ou supprimés. (Ils incluent des éléments qui ont été définis sur undefined , mais notre hypothétique TS à sécurité nulle l'interdirait.)

Mais l'accès aux tableaux classiques est un scénario important et j'aimerais voir une bonne solution pour cela. Faire des analyses complexes comme vous l'avez suggéré n'est clairement pas possible sauf dans des cas triviaux (mais importants car ils sont fréquents). Mais notez que même si vous pouviez prouver que i < array.length cela ne prouve pas que l'élément est initialisé.

Considérez l'exemple suivant, que pensez-vous que TS devrait faire ?

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

Il y a aussi le problème avec Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
  get() { return 10 },
})

Le mercredi 9 septembre 2015, 17:49 jods [email protected] a écrit :

@impinball https://github.com/impinball En effet, des API modernes telles que map,
forEach ou for..of sont ok car ils sautent des éléments qui n'ont jamais été
initialisé ou supprimé. (Ils incluent des éléments qui ont été définis sur
indéfini, mais notre hypothétique TS à sécurité nulle l'interdirait.)

Mais l'accès aux tableaux classiques est un scénario important et je voudrais
voir une bonne solution pour cela. Faire une analyse complexe comme vous l'avez suggéré est
clairement pas possible sauf dans des cas triviaux (cas pourtant importants puisque
ils sont communs). Mais notez que même si vous pouviez prouver que je <
array.length cela ne prouve pas que l'élément est initialisé.

Considérez l'exemple suivant, que pensez-vous que TS devrait faire ?

let tableau : T![] = []; // un tableau vide de T// non nul, non indéfini
tableau[4] = nouveau T(); // Très bien pour array[4], mais cela signifie que array[0..3] n'est pas défini, est-ce que ça va?// blah blahlet i = 2;// Notez que nous pourrions avoir un array bounds guardif (i < array. longueur) {
soit t = tableau[i] ; // Le type inféré serait T!, mais c'est en fait indéfini :(
}

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139055786
.

Tout ce que vous avez à faire est de :

  1. Rendez les types Null et Undefined référencés.
  2. Empêchez les valeurs nulles et indéfinies d'être attribuables à quoi que ce soit.
  3. Utilisez un type d'union avec Null et/ou Undefined où nullabilité est implicite.

C'est en effet un changement de rupture audacieux et semble plus approprié pour une langue
extension.
Le 10 septembre 2015 à 9 h 13, "Isiah Meadows" [email protected] a écrit :

Il y a aussi le problème avec Object.defineProperty.

let array = new Array(5)
Object.defineProperty(array, "length", 2, {
get() { return 10 },
})

Le mercredi 9 septembre 2015, 17:49 jods [email protected] a écrit :

@impinball https://github.com/impinball En effet, des API modernes telles que
carte,
forEach ou for..of sont ok car ils sautent des éléments qui n'ont jamais été
initialisé ou supprimé. (Ils incluent des éléments qui ont été définis sur
indéfini, mais notre hypothétique TS à sécurité nulle l'interdirait.)

Mais l'accès aux tableaux classiques est un scénario important et je voudrais
voir une bonne solution pour cela. Faire une analyse complexe comme vous l'avez suggéré est
clairement pas possible sauf dans des cas triviaux (cas pourtant importants puisque
ils sont communs). Mais notez que même si vous pouviez prouver que je <
array.length cela ne prouve pas que l'élément est initialisé.

Considérez l'exemple suivant, que pensez-vous que TS devrait faire ?

let tableau : T![] = []; // un tableau vide de T// non nul, non indéfini
bla bla
tableau[4] = nouveau T(); // Très bien pour array[4], mais cela signifie que array[0..3] sont
indéfini, c'est OK ?// blah blahlet i = 2;// Notez que nous pourrions avoir un
limites du tableau guardif (i < array.length) {
soit t = tableau[i] ; // Le type inféré serait T!, mais c'est en fait
indéfini :(
}

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139055786>
.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139230568
.

@impinball
Je me sens bien en ce qui concerne votre exemple. Utiliser defineProperty de cette manière, c'est sortir de la boîte de sécurité TS et entrer dans le domaine JS dynamique, vous ne pensez pas ? Je ne pense pas avoir déjà appelé defineProperty directement dans le code TS.

@aleksey-bykov

semble plus approprié pour une extension de langue.

Un autre problème avec cette proposition est qu'à moins qu'elle ne soit largement acceptée, elle a moins de valeur.
Finalement, nous avons besoin de définitions de TS mises à jour et je ne pense pas que cela se produira s'il s'agit d'une extension ou d'un fork de TS rarement utilisé et incompatible.

Je sais que les tableaux typés ont une garantie, car l'IIRC lance un
ReferenceError sur le chargement et le stockage du tableau hors limites. Des tableaux réguliers et
Les objets arguments renvoient undefined lorsque l'index est hors limites. Dans mon
opinion, c'est une faille du langage JS, mais la corriger serait très certainement
briser le Web.

La seule façon de "réparer" cela est dans ES6, via un constructeur renvoyant un proxy,
et son prototype d'instance et son auto-prototype définis sur le tableau d'origine
constructeur. Quelque chose comme ça:

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

(note : ce n'est pas testé, tapé sur un téléphone...)

Le jeu. 10 septembre 2015, 10:09 jods [email protected] a écrit :

@impinball https://github.com/impinball
Je me sens bien en ce qui concerne votre exemple. Utiliser defineProperty de cette manière est
sortir de la boîte de sécurité TS et entrer dans le domaine JS dynamique, ne
tu penses? Je ne pense pas avoir jamais appelé defineProperty directement dans le code TS.

@aleksey-bykov https://github.com/aleksey-bykov

semble plus approprié pour une extension de langue.

Un autre problème avec cette proposition est qu'à moins qu'elle ne soit largement acceptée, elle
a moins de valeur.
Finalement, nous avons besoin de définitions TS mises à jour et je ne pense pas que ce sera
se produire s'il s'agit d'une extension ou d'un fork rarement utilisé, incompatible de TS.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139245706
.

@impinball Pas sûr qu'il y ait quelque chose à "réparer" en premier lieu...
C'est la sémantique de JS et TS doit les accommoder d'une manière ou d'une autre.

Et si nous avions juste un balisage (légèrement différent) pour déclarer un tableau clairsemé par rapport à un tableau non clairsemé, et que le non clairsemé s'initialise automatiquement (peut-être que les programmeurs peuvent fournir une valeur ou une équation pour l'initialisation). De cette façon, nous pouvons forcer les tableaux clairsemés à être de type T|undefined (ce qui passerait au type T en utilisant for... of et d'autres opérations "sûres") et laisser les types de tableaux non clairsemés seuls.

//not-sparse
var array = [arrlength] => index*3;
var array = <number[]>[3];
//sparse
var array = [];

Évidemment, ce n'est pas la syntaxe finale.
Le deuxième exemple initialiserait chaque valeur à la valeur par défaut du compilateur pour ce type.
Cela signifie que pour les tableaux non clairsemés, vous devez les taper, sinon je suppose que vous devrez les convertir en quelque chose après leur initialisation complète.
Nous devrions également avoir besoin d'un type non sparse pour les tableaux afin que les programmeurs puissent convertir leurs tableaux en non sparse.

@Griffork
Je ne sais pas... ça devient confus.

De cette façon, nous pouvons forcer les tableaux clairsemés à être de type T|undefined (ce qui passerait au type T en utilisant for... of et d'autres opérations « sûres »)

À cause d'une bizarrerie dans JS, cela ne fonctionne pas de cette façon. Supposons let arr: (T|undefined)[] .
Je suis donc libre de faire : arr[0] = undefined .
Si je fais cela, l'utilisation de ces fonctions "sûres" renverra undefined pour le premier emplacement. Donc dans arr.forEach(x => ...) vous ne pouvez pas dire que x: T . Il doit toujours être x: T|undefined .

Le deuxième exemple initialiserait chaque valeur à la valeur par défaut du compilateur pour ce type.

Ce n'est pas très TS dans l'esprit. Peut-être que je me trompe, mais il me semble que la philosophie de TS est que les types ne sont qu'une couche supplémentaire au-dessus de JS et qu'ils n'ont pas d'impact sur le codegen. Cela a des implications de perf pour des raisons de correction de type partielle que je n'aime pas tout à fait.

TS ne peut évidemment pas vous protéger de tout dans JS et il existe plusieurs fonctions / constructions que vous pouvez appeler à partir de TS valides, qui ont des effets dynamiques sur le type d'exécution de vos objets et interrompent l'analyse statique de type TS.

Serait-ce mauvais si c'était un trou dans le système de type ? Je veux dire, ce code n'est vraiment pas courant : let x: number[] = []; x[3] = 0; et si c'est le genre de choses que vous voulez faire, vous devriez peut-être déclarer votre tableau let x: number?[] .

Ce n'est pas parfait mais je pense que c'est assez bon pour la plupart des utilisations dans le monde réel. Si vous êtes un puriste qui veut un système de type son, alors vous devriez certainement vous tourner vers une autre langue, car le système de type TS n'est de toute façon pas un son. Qu'en penses-tu?

C'est pourquoi j'ai dit que vous avez également besoin de la possibilité de convertir en un tableau non clairsemé
type, vous pouvez donc initialiser un tableau vous-même sans les performances
impacter.
Je me contenterais d'une différenciation entre (ce qui est censé être) clairsemé
tableaux et tableaux non fragmentés par type.

Pour ceux qui ne savent pas pourquoi c'est important, c'est la même raison pour laquelle vous
voudrait une différence entre T et T|null .

Le vendredi 11 septembre 2015 à 9h11, jods [email protected] a écrit :

@Griffork https://github.com/Griffork
Je ne sais pas... ça devient confus.

De cette façon, nous pouvons forcer les tableaux clairsemés à être de type T|undefined (ce qui
passer au type T en utilisant for... of et d'autres opérations « sûres »)

À cause d'une bizarrerie dans JS, cela ne fonctionne pas de cette façon. Supposons let arr :
(T|non défini)[].
Je suis donc libre de faire : arr[0] = undefined.
Si je fais cela, alors en utilisant ces fonctions "sûres" _will_ retournera undefined
pour le premier créneau. Donc dans arr.forEach(x => ...) vous ne pouvez pas dire que x:T.
Il doit toujours être x : T|undefined.

Le deuxième exemple initialiserait chaque valeur à la valeur par défaut du compilateur
pour ce genre.

Ce n'est pas très TS dans l'esprit. Peut-être que je me trompe mais il me semble
que la philosophie de TS est que les types ne sont qu'une couche supplémentaire au-dessus de JS
et ils n'ont pas d'impact sur codegen. Cela a des implications perf pour le bien de
correction de type partielle que je n'aime pas tout à fait.

TS ne peut évidemment pas vous protéger de tout en JS et il y a
plusieurs fonctions / constructions que vous pouvez appeler à partir d'un TS valide, qui ont
effets dynamiques sur le type d'exécution de vos objets et pause TS statique
analyse de type.

Serait-ce mauvais si c'était un trou dans le système de type ? Je veux dire, ce code
n'est vraiment pas courant : let x: number[] = []; x[3] = 0 ; et si c'est le
genre de choses que vous voulez faire, alors vous devriez peut-être déclarer votre tableau let
x : nombre ?[].

Ce n'est pas parfait mais je pense que c'est assez bon pour la plupart des utilisations dans le monde réel.
Si vous êtes un puriste qui veut un système de type son, alors vous devriez certainement
regardez dans une autre langue, car le système de type TS n'est pas sain de toute façon. Quoi
penses-tu?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139408240
.

@ jods4 Ce que je voulais dire par "réparer", c'était "réparer" ce qui est à mon

@jods Je me plains de JS, pas de TS. J'avoue que c'est hors sujet.

Le jeu. 10 septembre 2015, 19:19 Griffork [email protected] a écrit :

C'est pourquoi j'ai dit que vous avez également besoin de la possibilité de convertir en un tableau non clairsemé
type, vous pouvez donc initialiser un tableau vous-même sans les performances
impacter.
Je me contenterais d'une différenciation entre (ce qui est censé être) clairsemé
tableaux et tableaux non fragmentés par type.

Pour ceux qui ne savent pas pourquoi c'est important, c'est la même raison pour laquelle vous
voudrait une différence entre T et T|null .

Le vendredi 11 septembre 2015 à 9h11, jods [email protected] a écrit :

@Griffork https://github.com/Griffork
Je ne sais pas... ça devient confus.

De cette façon, nous pouvons forcer les tableaux clairsemés à être de type T|undefined (ce qui
passer au type T en utilisant for... of et d'autres opérations « sûres »)

À cause d'une bizarrerie dans JS, cela ne fonctionne pas de cette façon. Supposons let arr :
(T|non défini)[].
Je suis donc libre de faire : arr[0] = undefined.
Si je fais cela, alors en utilisant ces fonctions "sûres" _will_ retournera undefined
pour le premier créneau. Donc dans arr.forEach(x => ...) vous ne pouvez pas dire que x:T.
Il doit toujours être x : T|undefined.

Le deuxième exemple initialiserait chaque valeur à la valeur par défaut du compilateur
pour ce genre.

Ce n'est pas très TS dans l'esprit. Peut-être que je me trompe mais il me semble
que la philosophie de TS est que les types ne sont qu'une couche supplémentaire au-dessus de
JS
et ils n'ont pas d'impact sur codegen. Cela a des implications perf pour le bien de
correction de type partielle que je n'aime pas tout à fait.

TS ne peut évidemment pas vous protéger de tout en JS et il y a
plusieurs fonctions / constructions que vous pouvez appeler à partir d'un TS valide, qui
ont
effets dynamiques sur le type d'exécution de vos objets et pause TS statique
analyse de type.

Serait-ce mauvais si c'était un trou dans le système de type ? Je veux dire, ce code
n'est vraiment pas courant : let x: number[] = []; x[3] = 0 ; et si c'est le
genre de choses que vous voulez faire alors peut-être que vous devriez déclarer votre tableau
laisser
x : nombre ?[].

Ce n'est pas parfait mais je pense que c'est assez bon pour la plupart des utilisations dans le monde réel.
Si vous êtes un puriste qui veut un système de type son, alors vous devriez
assurément
regardez dans une autre langue, car le système de type TS n'est pas sain de toute façon.
Quoi
penses-tu?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
<
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-139408240>
.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -139409349
.

Et en ce qui concerne ma déclaration avec les longueurs de tableau, nous pourrions opérer en supposant que tous les accès aux tableaux sont dans les limites et que l'accès hors limites n'est pas défini à moins qu'il ne soit explicitement spécifié dans l'interface. C'est très similaire à ce que fait C/C++, et cela permettrait à la fois une meilleure frappe, et potentiellement toute une charge d'optimisations de compilateur, si quelqu'un décidait d'écrire un compilateur tiers qui utilise la spécification du langage, mais n'est pas aussi préoccupé par correspondant à l'émission.

Je sais que la prise en charge de la correspondance du comportement non défini C/C++ semble très stupide à première vue, mais je pense que dans ce cas, cela pourrait en valoir la peine. Il est rare de voir quelque chose qui est réellement fait _mieux_ en créant un accès hors limites. 99,99% des utilisations que j'ai vues pour cela ne sont que des odeurs de code extrêmement piquantes, presque toujours faites par des personnes qui n'ont presque aucune familiarité avec JavaScript.

(La plupart de ces personnes, d'après mon expérience, n'ont même pas entendu parler de CoffeeScript, encore moins de TypeScript. Beaucoup d'entre eux ne connaissent même pas la nouvelle version de JS qui vient d'être finalisée et standardisée, ES2015.)

Y a-t-il une résolution émergente à cela?

À moins d'avoir un type non nullable, il semble toujours utile que TypeScript échoue si l'on essaie d'accéder à une propriété sur une variable qui est _assurément_ null.

var o = null;
console.log(o.x);

... devrait échouer.

S'assurer à travers le système de types que tous les accès aux tableaux sont contrôlés par des limites semble dériver dans le domaine des types dépendants. Bien que les types dépendants soient assez soignés, cela semble être une fonctionnalité beaucoup plus importante que les types non nullables.

Il semble qu'il existe trois options en supposant que la vérification des limites n'est pas appliquée sur les tableaux au moment de la compilation :

  1. L'indexation de tableau (et tout accès arbitraire à un élément de tableau par index) est considérée comme renvoyant un type nullable, même sur des tableaux de types non nullables. Essentiellement, la "méthode" [] a une signature de type T? . Si vous savez que vous ne faites qu'une indexation avec vérification des limites, vous pouvez convertir le T? en T! dans le code de votre application.
  2. L'indexation de tableau renvoie exactement le même type (avec la même possibilité de nullité) que le paramètre de type générique du tableau, et il est supposé que tous les accès au tableau sont contrôlés par l'application. L'accès hors limites renverra undefined , et ne sera pas intercepté par le vérificateur de type.
  3. L'option nucléaire : tous les tableaux sont codés en dur dans le langage comme étant nullables, et les tentatives d'utilisation de tableaux non nullables échouent aux vérifications de type.

Tout cela s'applique également à l'accès basé sur l'index des propriétés sur les objets, par exemple object['property']object est de type { [ key: string ]: T! } .

Personnellement, je préfère la première option, où l'indexation dans un tableau ou un objet renvoie un type nullable. Mais même la deuxième option semble meilleure que tout étant nullable, ce qui est l'état actuel des choses. L'option nucléaire est grossière mais honnêtement aussi toujours meilleure que tout étant annulable.

Il y a une deuxième question de savoir si les types doivent être par défaut non nullables, ou par défaut nullables. Il semble que dans les deux cas, il serait utile d'avoir une syntaxe à la fois pour les types explicitement nullables et explicitement non nullables afin de gérer les génériques ; Par exemple, imaginez une méthode get sur une classe conteneur (par exemple une Map) qui prend une valeur et retourne éventuellement un type, _même si le conteneur ne contenait que des valeurs non nullables_ :

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

De même, nous pourrions (c'est un argument moins fort) vouloir nous assurer que notre classe de conteneur n'accepte que les clés non nulles lors des insertions, même en utilisant un type de clé nullable :

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

Ainsi, que la valeur par défaut change ou non, il semble qu'il serait utile d'avoir une syntaxe explicite pour les types Nullable et les types non Nullable. A moins que je rate quelque chose ?

Au point où il existe une syntaxe pour les deux, la valeur par défaut semble être un indicateur de compilateur similaire à --noImplicitAny . Personnellement, je voterais pour que la valeur par défaut reste la même jusqu'à une version 2.0, mais l'un ou l'autre semble bien tant qu'il y a une trappe de sortie (au moins temporairement).

Je préférerais la deuxième option, car même si cela rendrait l'accès hors limites un comportement indéfini (dans le domaine de la frappe TS), je pense que c'est un bon compromis pour cela. Cela peut augmenter considérablement la vitesse et c'est plus simple à gérer. Si vous vous attendez vraiment à ce qu'un accès hors limites soit possible, vous devez soit utiliser explicitement un type Nullable, soit convertir le résultat en un type Nullable (ce qui est toujours possible). Et généralement, si le tableau n'est pas nullable, tout accès hors limites est presque toujours un bogue, qui devrait éclater violemment à un moment donné (c'est une faille JS).

Cela nécessite à peu près que le programmeur soit explicite dans ce qu'il attend. C'est moins sûr de type, mais dans ce cas, je pense que la sécurité de type peut finir par gêner.

Voici une comparaison avec chaque option, en utilisant une fonction de sommation comme exemple (les primitives sont les plus problématiques) :

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

Autre exemple : une fonction map .

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

Une autre question : quel est le type de entry dans chacun d'eux ? !string , ?string , ou 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) { /* ... */  }

L'option trois était une suggestion un peu ironique :stuck_out_tongue:

Re : ta dernière question :

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

Dans certains cas - où vous souhaitez renvoyer un type non nullable et que vous l'obtenez à partir d'un tableau, par exemple - vous devrez effectuer un transtypage supplémentaire avec l'option un en supposant que vous avez garanti ailleurs qu'il n'y a pas de valeurs indéfinies dans le tableau (l'exigence de cette garantie ne change pas quelle que soit l'approche adoptée, il suffit de taper as string! ). Personnellement, je le préfère toujours car il est à la fois plus explicite (vous devez spécifier quand vous adoptez un comportement potentiellement dangereux, par opposition à ce qu'il se produise implicitement) et plus cohérent avec le fonctionnement de la plupart des classes de conteneurs : par exemple, un get Map Map.prototype.get renvoie un nullable alors object['property'] devrait probablement le faire les mêmes, car ils offrent des garanties similaires sur la nullité et sont utilisés de la même manière. Ce qui laisse les tableaux impairs où les erreurs de référence nulles peuvent revenir et où l'accès aléatoire est autorisé à être non nul par le système de types.

Il existe certainement d'autres approches; par exemple, Flow utilise actuellement l'option deux , et la dernière fois que j'ai vérifié que SoundScript rendait les tableaux clairsemés explicitement illégaux dans leurs spécifications (enfin, le mode fort/"SaneScript" les rend illégaux, et SoundScript est un sur-ensemble des nouvelles règles), ce qui dans une certaine mesure contourne le problème bien qu'ils aient encore besoin de comprendre comment gérer les changements de longueur manuels et l'allocation initiale. Je soupçonne qu'ils se rapprocheront de l'option un dans le compromis commodité vs sécurité - c'est-à-dire qu'il sera moins pratique à écrire mais plus sûr - en raison de l'accent mis sur la solidité du système de type, mais il sera probablement quelque peu différent que l'une ou l'autre de ces approches en raison de l'interdiction des tableaux clairsemés.

Je pense que l'aspect des performances est extrêmement théorique à ce stade, car AFAIK TypeScript continuera à émettre le même JS quel que soit le choix ici et les machines virtuelles sous-jacentes continueront de vérifier les limites sous le capot malgré tout. Je ne suis donc pas trop influencé par cet argument. La question à mon avis concerne principalement la commodité par rapport à la sécurité ; pour moi, la sécurité gagnée ici semble valoir le compromis de commodité. Bien sûr, l'un ou l'autre est une amélioration par rapport au fait que tous les types soient nullables.

Je suis d'accord que la partie performance est principalement théorique, mais je
comme la commodité d'assumer. La plupart des tableaux sont denses et la nullité par
default n'a pas de sens pour les tableaux booléens et numériques. Si ce n'est pas
destiné à être un tableau dense, il doit être marqué comme explicitement nullable afin
l'intention est claire.


TypeScript a vraiment besoin d'un moyen d'affirmer les choses, car les assertions sont souvent
utilisé pour aider à la vérification de type statique dans d'autres langues. j'ai vu dans le
Le code V8 base une macro UNREACHABLE(); qui permet aux hypothèses d'être un
un peu plus sûr, planter le programme si l'invariant est violé. C++
a static_assert pour les assertions statiques pour aider à la vérification de type.

Le mardi 20 octobre 2015 à 04h01, Matt Baker [email protected]
a écrit:

L'option trois était un peu une suggestion ironique [image :
:stuck_out_tongue:]

Re : ta dernière question :

declare const regularStrings: string[];declare const nullableStrings: string?[];declare const nonNullableStrings: string![]; // échoue la vérification de type dans l'option trois
for(entrée constante de regularStrings) {
// option 1 : l'entrée est de type chaîne ?
// option 2 : dépend de la nullabilité par défaut
}
for(entrée constante de nullableStrings) {
// option 1 et 2 : l'entrée est de type chaîne ?
}
for(entrée constante de nonNullableStrings) {
// option 1 : l'entrée est de type chaîne ?
// option 2 : l'entrée est de type chaîne !
}

Dans certains cas, lorsque vous souhaitez renvoyer un type non nullable et que vous êtes
l'obtenir à partir d'un tableau, par exemple - vous devrez faire un casting supplémentaire
avec l'option un en supposant que vous avez garanti ailleurs qu'il n'y a pas d'indéfini
valeurs dans le tableau (l'exigence de cette garantie ne change pas
quelle que soit l'approche adoptée, seul le besoin de taper sous forme de chaîne !).
Personnellement je le préfère quand même car c'est à la fois plus explicite (il faut
préciser quand vous adoptez un comportement potentiellement dangereux, par opposition à celui-ci
se passe implicitement) et plus cohérent avec la façon dont la plupart des classes de conteneurs
work : par exemple, la fonction get d'une Map renvoie clairement des types nullables
(il renvoie l'objet s'il existe sous la clé, ou null s'il n'existe pas),
et si Map.prototype.get renvoie un nullable alors object['property']
devraient probablement faire de même, car ils font des garanties similaires sur
nullabilité et sont utilisés de la même manière. Ce qui laisse les tableaux comme les impairs
où les erreurs de référence nulles peuvent revenir, et où l'accès aléatoire est
autorisé à être non nullable par le système de types.

Il existe certainement d'autres approches; par exemple, Flow utilise actuellement
option deux http://flowtype.org/docs/nullable-types.html , et dernier I
vérifié SoundScript a rendu les tableaux clairsemés explicitement illégaux dans leurs spécifications
https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/JSExperimentalDirections.pdf
(enfin, le mode fort/"SaneScript" les rend illégaux, et SoundScript est un
super-ensemble des nouvelles règles), ce qui contourne dans une certaine mesure le problème
même s'ils devront encore trouver comment gérer la longueur manuelle
changements et avec l'attribution initiale.

Je pense que l'aspect performance est extrêmement théorique à ce stade,
puisque AFAIK TypeScript continuera à émettre le même JS indépendamment de
lance pour n'importe quel choix ici et les machines virtuelles sous-jacentes continueront
tableaux de vérification des limites sous le capot, peu importe. Donc je ne suis pas trop influencé par
cet argument. La question à mon avis concerne principalement la commodité par rapport à la commodité.
sécurité; pour moi, la sécurité gagnée ici semble valoir le compromis de commodité. De
Bien sûr, l'un ou l'autre est une amélioration par rapport au fait que tous les types soient nullables.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -149468527
.

Isiah Meadows

Devrions-nous simplement commencer à les appeler des types non-void ? Dans tous les cas, je pense que définir explicitement non-void avec T! ou !T est une erreur. Il est difficile à lire en tant qu'humain, et également difficile à traiter avec tous les cas pour le compilateur TypeScript.

Le principal problème que je vois avec les types non-void par défaut est qu'il s'agit d'un changement radical. Et si nous ajoutions simplement une analyse plus statique, similaire à Flow, cela ne changerait pas du tout le comportement, mais attraperait plus de bogues ? Ensuite, nous pouvons détecter la plupart des bogues de cette classe maintenant, mais ne changez pas la syntaxe, et à l'avenir, il sera beaucoup plus facile d'introduire un indicateur de compilateur ou un comportement par défaut qui soit moins un changement de rupture.

``` .ts
// la fonction compile sans problème
fonction len(x : chaîne) : nombre {
renvoie x.longueur ;
}

len("fonctionne"); // 5
len(nulle); // erreur, aucune longueur de propriété de null

``` .ts
function len(x: string): number {
    if (x === null) {
        return -1;
    }
    return x.length;
}

len("works"); // 5
len(null); // null

Ce qui se passe vraiment ici, c'est de modéliser les données d'entrée sous la forme non-void , mais en ajoutant implicitement void lorsqu'elles sont gérées dans la fonction. De même, le type de retour est non-void moins qu'il ne puisse explicitement retourner null ou undefined

On peut aussi ajouter le type ?T ou T? , qui force la vérification nulle (et/ou indéfinie) avant utilisation. Personnellement, j'aime T? , mais il existe un précédent pour utiliser ?T avec Flow.

``` .ts
fonction len(x: ?string): nombre {
renvoie x.longueur ; // erreur : aucune propriété de longueur sur le type ? chaîne, vous devez utiliser un type guard
}

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

Sous le capot, ce qui se passe vraiment ici, c'est que TypeScript déduit si un type peut être null ou non en voyant s'il gère null, et reçoit une valeur éventuellement null.

La seule partie délicate ici est de savoir comment s'interfacer avec les fichiers de définition. Je pense que cela peut être résolu en supposant par défaut qu'un fichier de définition déclarant un function(t: T) effectue void vérification null/

Cela permet maintenant deux choses :

  1. Adoption progressive de la syntaxe de type ?T , dont les paramètres optionnels seraient déjà permutés.
  2. À l'avenir, un indicateur de compilateur --noImplicitVoid , pourrait être ajouté qui traiterait les fichiers de déclaration de la même manière que les fichiers de code compilés. Ce serait « destructeur », mais si cela est fait plus tard, la majorité des bibliothèques adopteront la meilleure pratique consistant à utiliser ?T lorsque le type peut être void et T quand il ne peut pas. Ce serait également opt-in, de sorte que seuls ceux qui choisissent de l'utiliser seraient affectés. Cela pourrait également nécessiter l'utilisation ?T la syntaxe

Je pense que c'est une approche réaliste, car elle offre une sécurité bien améliorée lorsque la source est disponible en TypeScript, en trouvant ces problèmes délicats, tout en permettant une intégration facile, assez intuitive et rétrocompatible avec les fichiers de définition.

La variante de préfixe ? est également utilisée dans les annotations du compilateur de fermeture, IIRC.

Le mar 17 novembre 2015, 13:37 Tom Jacques [email protected] a écrit :

Devrions-nous simplement commencer à les appeler des types non nuls ? En tout cas je pense
définissant explicitement le non-vide avec T! ou !T est une erreur. Il est difficile
à lire en tant qu'humain, et aussi difficile de traiter tous les cas pour
le compilateur TypeScript.

Le principal problème que je vois avec les types non vides par défaut est qu'il s'agit d'un
changement de rupture. Eh bien, et si nous ajoutions simplement une analyse plus statique,
similaire à Flow, cela ne change pas du tout le comportement, mais attrapera
plus de bugs ? Ensuite, nous pouvons attraper la plupart des bogues de cette classe maintenant, mais
ne changez pas la syntaxe, et à l'avenir, il sera beaucoup plus facile de
introduire un indicateur de compilateur ou un comportement par défaut moins perturbateur
monnaie.

// la fonction compile heureusementfunction len(x: string): nombre {
renvoie x.longueur ;
}

len("fonctionne"); // 5
len(nulle); // erreur, aucune longueur de propriété de null

fonction len(x : chaîne) : nombre {
si (x === nul) {
retour -1 ;
}
renvoie x.longueur ;
}

len("fonctionne"); // 5
len(nulle); // nul

Ce qui se passe vraiment ici, c'est la modélisation des données d'entrée comme non vides,
mais en ajoutant implicitement un vide lorsqu'il est géré dans la fonction. De la même manière,
le type de retour est non nul à moins qu'il ne puisse explicitement retourner null ou
indéfini

Nous pouvons également ajouter le ?T ou T? type, qui force le null (et/ou
non défini) vérifier avant utilisation. Personnellement j'aime bien T?, mais il y a des précédents
pour utiliser ?T avec Flow.

fonction len(x: ?string): nombre {
renvoie x.longueur ; // erreur : aucune propriété de longueur sur le type ? chaîne, vous devez utiliser un type guard
}

Un autre exemple : qu'en est-il de l'utilisation des résultats de la fonction ?

fonction len(x : chaîne) : nombre {
renvoie x.longueur ;
}
identité de fonction (f: chaîne): chaîne {
retour f;
}
fonction inconnue() : chaîne {
if (Math.aléatoire() > 0.5) {
renvoie null ;
}
retourner "peut-être" ;
}

len("fonctionne"); // 5
len(nulle); // erreur, aucune longueur de propriété de null

identité("travaux"); // "fonctionne": chaîne
identité (nulle); // null : nul
inconnue(); // ?chaîne de caractères

len(identité("fonctionne")); // 5
len(identité(null)); // erreur, aucune longueur de propriété de null
len(inconnu()); // erreur : aucune propriété de longueur sur le type ? chaîne, vous devez utiliser un type guard

Sous le capot, ce qui se passe vraiment ici, c'est que TypeScript est
déduire si un type peut être nul ou non en voyant s'il gère
null, et reçoit une valeur éventuellement nulle.

La seule partie délicate ici est de savoir comment s'interfacer avec les fichiers de définition. je
pense que cela peut être résolu en ayant la valeur par défaut de supposer qu'un
fichier de définition déclarant une fonction (t: T) effectue une vérification null/void, beaucoup
comme le deuxième exemple. Cela signifie que ces fonctions pourront prendre
valeurs nulles sans que le compilateur génère une erreur.

Cela permet maintenant deux choses :

  1. Adoption progressive de la syntaxe de type ?T dont optionnelle
    paramètres seraient déjà échangés.
  2. À l'avenir, un indicateur de compilateur --noImplicitVoid, pourrait être ajouté
    qui traiterait les fichiers de déclaration de la même manière que les fichiers de code compilés. Cette
    serait « rupture », mais si cela se fait loin sur la route, la majorité des
    les bibliothèques adopteront la meilleure pratique d'utiliser ?T lorsque le type peut
    être nul et T quand il ne le peut pas. Il s'agirait également d'un opt-in, de sorte que seuls ceux
    qui choisissent de l'utiliser serait affecté.

Je pense qu'il s'agit d'une approche réaliste, car elle améliore considérablement la sécurité
lorsque la source est disponible dans TypeScript, trouver ces problèmes délicats,
tout en permettant des fonctionnalités simples, assez intuitives et rétrocompatibles
intégration avec les fichiers de définition.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment -157463734
.

Bon point. Il existe également de nombreuses similitudes avec les déclarations de paramètres facultatifs existantes :

``` .ts
interface withOptionalProperty {
o?: chaîne
}
interface avecVoidableProperty {
o : ?chaîne
}

function withOptionalParam(o?: string) { }
function withVoidableParam(o: ?string) { }
```

En fait, ils utilisent le préfixe . Ils utilisent ? pour les types Nullable explicites et ! pour les types non Nullable, nullable étant la valeur par défaut.

La distinction annulable vs nullable a beaucoup de sens. :+1:

J'ai l'impression que ça tourne en rond.

Avez-vous tous lu ci-dessus pourquoi une définition annulable/non annulable ne résoudra pas le problème sous-jacent ?

@Griffork J'ai lu tous les commentaires. J'admets que ce que j'ai dit est en quelque sorte une répétition / combinaison de ce que d'autres ont dit, mais je pense que c'est la voie à suivre la plus réaliste. Je ne sais pas ce que vous voyez comme problème sous-jacent, mais pour moi le problème sous-jacent est que null et undefined font partie de chaque type, et le compilateur n'assure actuellement pas la sécurité en essayant pour utiliser un argument de type T . D'après mon exemple :

``` .ts
fonction len(x : chaîne) : nombre {
renvoie x.longueur ;
}

len("fonctionne");
// Pas d'erreur -- 5

len(nulle);
// Le compilateur autorise cela mais devrait générer une erreur ici avec quelque chose comme
// erreur : aucune propriété 'longueur' de null

len(non défini);
// Le compilateur autorise cela mais devrait générer une erreur ici avec quelque chose comme
// erreur : aucune propriété 'longueur' non définie
```

C'est mon point de vue sur le problème fondamental avec le _language_ -- le manque de sécurité. Une grande partie de cela peut être corrigée en examinant le flux d'arguments dans les fonctions à l'aide d'une analyse statique et de type, et en donnant une erreur lorsque quelque chose pourrait être fait de manière dangereuse. Il n'y a pas besoin du tout de type ?T lorsque le code est disponible car cette analyse peut être effectuée (bien que pas dans tous les cas avec une précision parfaite tout le temps). La raison de l'ajout d'un type ?T est qu'il force le contrôle de sécurité et rend l'intention du programmeur très claire.

Le problème avec l'_implémentation_ est la rétrocompatibilité. Si l'équipe TS publiait demain une modification selon laquelle désormais un type T n'est pas nul par défaut, cela briserait le code existant qui accepte et gère actuellement les entrées nulles avec ces signatures de type. Les membres de l'équipe TS ont déclaré dans ce même numéro qu'ils n'étaient pas disposés à faire un changement aussi important. Ils sont prêts à casser certaines choses, si l'effet est suffisamment faible et le bénéfice suffisamment important, mais cela aurait un impact trop important.

Ma proposition est en deux parties distinctes, dont l'une serait à mon avis un excellent ajout au langage qui ne change rien à la syntaxe/sémantique existante, sauf pour trouver de vrais vrais bogues, et l'autre est un moyen possible de réduire l'impact du changement de rupture pour obtenir les garanties les plus fortes des types non vides à l'avenir. Il s'agit toujours d'un changement décisif, mais d'une taille et d'une nature, espérons-le, plus acceptables.

Partie un:

  • Ajoutez une analyse pour identifier quand les types null/undefined/void peuvent être passés à des fonctions qui ne les gèrent pas (ne fonctionne que lorsque le code TS est présent, pas dans les fichiers de définition).
  • Ajoutez le type ?T qui force la vérification void avant d'utiliser l'argument. C'est vraiment juste du sucre syntaxique autour d'un type d'option au niveau du langage.
  • Ces deux fonctionnalités peuvent être mises en œuvre indépendamment car chacune a son propre mérite.

Deuxième partie:

  • Attendez. Plus tard, après l'introduction de la première partie, et la communauté et les utilisateurs de TS ont eu le temps d'utiliser ces fonctionnalités, la norme sera d'utiliser T lorsque le type n'est pas nul, et ?T lorsque le type peut être nul. Ce n'est pas garanti, mais je pense que ce serait une meilleure pratique évidente.
  • En tant que fonctionnalité distincte, ajoutez une option de compilateur --noImplicitVoid qui exige que les types soient ?T s'ils peuvent être nuls. Il ne s'agit que du compilateur appliquant les meilleures pratiques déjà existantes. S'il existe un fichier de définition qui ne respecte pas les meilleures pratiques, il serait incorrect, mais c'est pourquoi il est opt-in.
  • Si vous vouliez vraiment être strict, le drapeau pourrait accepter des arguments spécifiant à quels répertoires/fichiers il doit s'appliquer. Ensuite, vous pouvez appliquer la modification uniquement à votre code et exclure node_modules .

Je pense que c'est l'option la plus réaliste car la première partie peut être réalisée même sans la deuxième partie. C'est toujours une bonne fonctionnalité qui atténuera largement ce problème. Bien sûr, ce n'est pas parfait, mais je prendrai assez bien si cela signifie que cela peut arriver. Cela laisse également l'option sur la table pour les vrais types non nuls à l'avenir. Un problème en ce moment est que plus ce problème persiste, plus il est important de le corriger car il y a plus de code écrit dans TS. Avec la première partie, cela devrait au pire ralentir considérablement cela et, au mieux, réduire l'impact au fil du temps.

@tejacques Pour ma part, je suis totalement d'

@tejacques - FWIW, je suis tout à fait d'accord avec ton appréciation et ta proposition. Espérons que l'équipe TS soit d'accord :)

En fait, deux choses :

Premièrement, je ne suis pas sûr que l'analyse de type Flow mentionnée dans la première partie soit nécessaire. Bien que ce soit très cool et utile, je ne voudrais certainement pas qu'il conserve les types voidable/ ?T , qui semblent beaucoup plus réalisables dans la conception actuelle du langage et offrent une valeur beaucoup plus à long terme.

Je formulerais également --noImplicitVoid un peu différemment - disons que cela interdit d'attribuer null et undefined à des types non annulables (c'est-à-dire par défaut) plutôt que "exiger que les types soient ?T s'ils peuvent être nuls". Je suis à peu près sûr que nous voulons dire la même chose, juste de la sémantique ; se concentre sur l'utilisation plutôt que sur la définition, qui, si je comprends comment fonctionne TS, sont les seules choses qu'il peut réellement appliquer.

Et quelque chose m'est venu à l'esprit tout à l'heure : nous aurions alors quatre niveaux d'annulation (c'est également le cas de Flow, qui, je pense, inspire une bonne partie de cette conversation) :

interface Foo {
  w: string;
  x?: string;
  y: ?string;
  z?: ?string;
}

Sous --noImplicitVoid , w ne peut être qu'une chaîne valide. C'est une énorme victoire pour la sécurité de type. Au revoir, erreur d'un milliard de dollars ! Sans --noImplicitVoid , bien sûr, la seule contrainte est qu'il doit être spécifié, mais il peut être nul ou indéfini. C'est un comportement assez dangereux de la langue, je pense, car il semble qu'il garantit plus qu'il ne l'est en réalité.

x est complètement indulgent dans les paramètres actuels. Il peut s'agir d'une chaîne, nulle, non définie, et il n'est même pas nécessaire qu'il soit présent dans les objets qui l'implémentent. En dessous de --noImplicitVoid , les choses deviennent un peu plus complexes... vous ne le définissez peut-être pas, mais si vous _faites_ quelque chose, cela ne peut-il pas être nul ? Je pense que la façon dont Flow gère cela est que vous pouvez définir x sur undefined (imitant la non-existence), mais pas null . Cela pourrait être un peu trop opiniâtre pour TypeScript, cependant.

En outre, il serait logique d'exiger une vérification des vides x avant de l'utiliser, mais ce serait encore une fois un changement décisif. Ce comportement pourrait-il faire partie du drapeau --noImplicitVoid ?

y peut être défini sur n'importe quelle chaîne ou valeur void, mais _doit_ être présent sous une certaine forme, même s'il est void. Et, bien sûr, y accéder nécessite un chèque annulé. Ce comportement peut être un peu surprenant. Devrions-nous considérer _pas_ le définir comme étant le même que le définir sur undefined ? Si c'est le cas, qu'est-ce qui le rendrait différent de x , à part l'exigence d'un chèque annulé ?

Et enfin, z n'a pas besoin d'être spécifié, peut être défini sur absolument n'importe quoi (enfin, sauf une non-chaîne bien sûr) et (pour une bonne raison) nécessite une vérification d'annulation avant d'y accéder. Tout a du sens ici !

Il y a un peu de chevauchement entre x et y , et je soupçonne que x finirait par devenir obsolète par la communauté, préférant la forme z pour une sécurité maximale .

@tejacques

Il n'y a pas besoin du tout de type ?T lorsque le code est disponible car cette analyse peut être effectuée (bien que pas toujours parfaitement précise dans tous les cas).

Cette déclaration est incorrecte. De nombreux aspects de la nullabilité ne peuvent pas être pris en compte, même avec le code source complet disponible.

Par exemple : lors de l'appel dans une interface (statiquement), vous ne pouvez pas savoir si passer null est correct ou non si l'interface n'est pas annotée en conséquence. Dans un système de type structurel tel que TS, il n'est même pas facile de savoir quels objets sont des implémentations de l'interface et lesquels ne le sont pas. En général, cela vous est égal, mais si vous voulez déduire la nullité du code source, vous le ferez.

Autre exemple : si j'ai un tableau list et une fonction unsafe(x) dont le code source montre qu'il n'accepte pas un argument null , le compilateur ne peut pas dire si cela la ligne est sûre ou non : list.filter(unsafe) . Et en fait, à moins que vous ne puissiez savoir statiquement quel sera tout le contenu possible de list , cela ne peut pas être fait.

D'autres cas sont liés à l'héritage et plus encore.

Je ne dis pas qu'un outil d'analyse de code qui signalerait une violation flagrante de contrats nuls n'a aucune valeur (c'est le cas). Je signale simplement que minimiser l'utilité des annotations nulles lorsque le code source est disponible est à mon humble avis une erreur.

J'avais dit quelque part dans cette discussion que je pense que l'inférence de nullabilité pourrait aider à réduire la compatibilité descendante dans de nombreux cas simples. Mais il ne peut pas remplacer complètement les annotations source.

@tejacques mon mauvais, j'ai mal lu votre commentaire (pour une raison quelconque, mon cerveau a décidé void===null :( Je blâme juste de me réveiller).
Merci pour le post supplémentaire cependant, il a eu beaucoup plus de sens pour moi que votre message d'origine. En fait, j'aime bien cette idée.

Je vais répondre à vous trois séparément car l'écrire sur un seul message est un blob illisible.

@Griffork Pas de problème. Parfois, il est difficile de tout transmettre correctement par le biais d'un texte et je pense que cela valait la peine de clarifier de toute façon.

@dallonf Votre reformulation est exactement ce que je veux dire - nous sommes sur la même

Je pense que la différence entre x?: T et y: ?T résiderait dans l'utilisation des info-bulles/fonctions, ainsi que dans la saisie et la garde utilisées.

Déclarer comme argument facultatif modifie l'utilisation des info-bulles/fonctions, il est donc clair que c'est facultatif :
a?: T n'a pas besoin d'être passé comme argument dans l'appel de fonction, peut être laissé vide
Si une déclaration n'a pas ?: elle doit être passée en argument dans l'appel de fonction et ne peut pas être laissée vide

w: T est un argument non void obligatoire (avec --noImplicitVoid )
ne nécessite pas de garde

x?: T est un argument facultatif, donc le type est vraiment T | undefined
nécessite un if (typeof x !== 'undefined') garde.
Notez le triple glyphe !== pour une vérification exacte sur undefined .

y: ?T est un argument obligatoire, et le type est en réalité T | void
nécessite un if (y == null) garde.
Notez le double glyphe == qui correspond à la fois à null et à undefined soit void

z?: ?T est un argument facultatif, et le type est en réalité T | undefined | void qui est T | void
nécessite un if (z == null) garde.
Notez à nouveau le double glyphe == qui correspond à la fois à null et à undefined soit void

Comme pour tous les arguments facultatifs, vous ne pouvez pas avoir d'arguments obligatoires qui suivent des arguments facultatifs. Voilà donc quelle serait la différence. Maintenant, vous pouvez aussi simplement faire une garde null sur l'argument facultatif, et cela fonctionnerait aussi, mais une différence clé est que vous ne pouvez pas passer la null à la fonction si vous l'a appelé; cependant, vous pouvez passer undefined .

Je pense que tous ces éléments ont en fait une place, donc cela ne déprécierait pas nécessairement la syntaxe actuelle des arguments optionnels, mais je suis d'accord pour dire que la forme z est la plus sûre.

Edit : Mise à jour du libellé et correction de quelques fautes de frappe.

@jods4 Je suis d'accord avec à peu près tout ce que vous avez dit. Je n'essaie pas de minimiser l'importance de la non- void dactylographie. J'essaie juste de pousser une phase à la fois. Si l'équipe TS ne peut pas le faire plus tard, au moins nous sommes mieux lotis, et s'ils peuvent le faire plus tard après avoir mis en œuvre plus de contrôles et ?T alors c'est mission accomplie.

Je pense que le cas avec Arrays est vraiment délicat. Tu peux toujours faire quelque chose comme ça :

``` .ts
fonction numVersChaîne(x : nombre) {
return x.toString();
}
var nums : nombre[] = Array(100);
numVersChaîne(nums[0]); // Vous êtes foutus!

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

Cela ne vous aide vraiment pas beaucoup, car c'est inattendu, mais ce n'est pas une erreur dans le vrai JavaScript aujourd'hui.

La seule autre chose que je veux souligner est que vous pouvez progresser même avec les interfaces, c'est juste beaucoup plus de travail et d'effort via l'analyse statique que via le système de type, mais ce n'est pas trop différent de ce qui se passe déjà maintenant.

Voici un exemple. Supposons que --noImplicitVoid est désactivé

``` .ts
interface ITransform{
(x : T) : U ;
}

interface IHaveName {
nom : chaîne ;
}

transformation de fonction(x : T, fn : ITransformer) {
renvoie fn(x);
}

var nommé = {
nom : "Fou"
} ;

var nom incorrect = {
nom : 1234
} ;

var nomméNull = {
nom : nul
} ;

var someFun = (x: IHaveName) => x.name;
var someFunHandlesVoid = (x: IHaveName) => {
if (x != null && x.name != null) {
renvoie x.name ;
}
renvoie « Sans nom » ;
} ;

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"

Vous avez raison, vous ne pouvez pas tout attraper, mais vous pouvez attraper beaucoup de choses.

Remarque finale : quel devrait être le comportement de ce qui précède lorsque --noImplicitVoid est activé ?

Maintenant, someFun et someFunHandlesVoid sont tous les deux vérifiés de la même manière et produisent les mêmes messages d'erreur que someFun . Même si someFunHandlesVoid gère void, l'appeler avec un null ou undefined est une erreur car la signature indique qu'il ne prend pas void . Il devrait être saisi sous la forme (x: ?IHaveName) : string pour accepter null ou undefined . Si nous changeons son type, il continue à fonctionner comme avant.

C'est la partie qui est un changement décisif, mais tout ce que nous avons à faire pour le corriger était d'ajouter un seul caractère ? à la signature de type. Nous pouvons même avoir un autre indicateur --warnImplicitVoid qui fait la même chose qu'un avertissement afin que nous puissions migrer lentement.

Je me sens complètement abruti pour faire ça, mais je vais faire un autre post.

À ce stade, je ne sais pas quoi faire pour continuer. Y a-t-il une meilleure idée? Devrions nous:

  • continuer à discuter/préciser comment cela devrait se comporter ?
  • transformer cela en trois nouvelles propositions de fonctionnalités ?

    • Analyse améliorée

    • Peut-être/Type d'option ?T

    • Option de compilation --noImplicitVoid

  • ping membres de l'équipe TypeScript pour la contribution ?

Je penche pour de nouvelles propositions et je poursuis la discussion là-bas, car il est presque inhumain de demander à l'équipe TypeScript de rattraper son retard sur ce fil compte tenu de sa durée.

@tejacques

  • Il vous manque un typeof dans l'exemple triple égal à dallonf.
  • Il semble qu'il vous manque quelque ? dans l'exemple de jods4.

Autant je pense que les choses devraient rester dans ce fil, je pense que ce fil n'est plus vraiment "regardé" (peut-être plus comme un coup d'œil de temps en temps). Donc, créer de nouveaux threads créerait certainement de la traction.
Mais attendez quelques jours/une semaine pour que les gens lèvent la tête et fournissent d'abord des commentaires. Vous voudrez que votre proposition soit assez solide.

Edit : la démarque mémorisée existe.

Commenter ce fil est assez futile à ce stade. Même si une proposition est faite que l'équipe TypeScript considère comme acceptable (j'ai essayé cela ci-dessus en août), il n'y a aucun moyen qu'elle la trouve parmi le bruit.

Le mieux que vous puissiez espérer est que le niveau d'attention soit suffisant pour que l'équipe TypeScript propose sa propre proposition et la mette en œuvre. Sinon, oubliez-le et utilisez Flow.

+1 pour diviser cela, mais pour l'instant, l'option --noImplicitVoid peut
attendez que le type nullable soit implémenté.

Jusqu'à présent, nous nous sommes surtout mis d'accord sur la syntaxe et la sémantique de
types nullables, donc si quelqu'un pouvait rédiger une proposition et une mise en œuvre
de celui-ci, ce serait de l'or. J'ai une proposition d'un processus similaire
concernant les énumérations d'autres types, mais je n'ai tout simplement pas eu le temps de
le mettre en œuvre grâce à d'autres projets.

Le mercredi 18 novembre 2015, à 21 h 24, Tom Jacques [email protected] a écrit :

J'ai l'impression d'être un con pour faire ça, mais je vais en faire un de plus
Publier.

À ce stade, je ne sais pas quoi faire pour continuer. Y a-t-il une meilleure idée?
Devrions nous:

  • continuer à discuter/préciser comment cela devrait se comporter ?
  • transformer cela en trois nouvelles propositions de fonctionnalités ?

    • Analyse améliorée

    • Peut-être/Type d'option ?T

    • --noImplicitVoid option du compilateur

  • ping membres de l'équipe TypeScript pour la contribution ?

Je penche pour de nouvelles propositions et je poursuis la discussion là-bas depuis
il est presque inhumain de demander à l'équipe TypeScript de rattraper son retard sur ce fil
vu combien de temps c'est.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/185#issuecomment-157928828
.

+1 pour l'option --noImplicitNull (interdit l'annulation et l'affectation nulle).

J'ai essayé d'atténuer ce problème avec un type spécial Op<A> = A | NullType . Cela semble plutôt bien fonctionner. Voir ici .

+1 pour _--noImplicitNull_ aussi S'IL VOUS PLAÎT :+1 :

+1 pour --noImplicitNull

Cela devrait-il être fermé?

@Gaelan Given #7140 est fusionné, si vous souhaitez déposer un nouveau numéro dédié pour --noImplicitNull comme suggéré par quelques personnes ici, alors il est probablement sûr de le faire maintenant.

@isiahmeadows Il vaudrait probablement mieux laisser cela ouvert alors.

Cela devrait-il être fermé?

Nous pensons que https://github.com/Microsoft/TypeScript/issues/2388 est la partie renommage de ce travail. C'est pourquoi nous n'avons pas encore déclaré cette fonctionnalité complète.

si vous souhaitez déposer un nouveau numéro dédié pour --noImplicitNull comme suggéré par quelques personnes ici, alors vous pouvez probablement le faire en toute sécurité maintenant.

Je ne suis pas sûr de comprendre la sémantique demandée pour ce nouveau drapeau. Je recommanderais d'ouvrir un nouveau numéro avec une proposition claire.

@mhegazy L'idée posée plus tôt dans ce numéro pour --noImplicitNull était que tout doit être explicitement ?Type ou !Type . À mon humble avis, je ne pense pas que cela vaut la peine d'être utilisé lorsqu'il existe un autre indicateur qui induit par défaut non nullable que IIRC a déjà été implémenté alors que les types nullables eux-mêmes l'étaient.

Clôture maintenant que #7140 et #8010 sont tous deux fusionnés.

Désolé si je commente un problème clos mais je ne connais pas de meilleur endroit où demander et je ne pense pas que cela vaut la peine d'un nouveau numéro s'il n'y a pas d'intérêt.
Serait-il possible de gérer les valeurs nulles implicites par fichier ?
Par exemple, gérer un tas de fichiers td avec noImplicitNull (car ils proviennent de définitivement typés et ont été conçus de cette façon) mais gérer ma source comme impliciteNull ?
Est-ce que quelqu'un trouverait cela utile ?

@massimiliano-mantione, veuillez consulter https://github.com/Microsoft/TypeScript/issues/8405

Cette page vous a été utile?
0 / 5 - 0 notes