JavaScript: le grand tout bien pourquoi


Il n'y a pas si longtemps, JavaScript offrait un nouveau type de données BigInt primitif pour travailler avec des nombres de précision arbitraires. Le minimum d'informations nécessaires a déjà été dit / traduit sur la motivation et les cas d'utilisation. Et je voudrais prêter un peu plus d'attention à l '"explicitness" local dépassé dans la conversion de type et TypeError inattendu. Allons-nous gronder ou comprendre et pardonner (encore)?

Implicite devient explicite?


Dans un langage où la conversion de type implicite est utilisée depuis longtemps, elle est devenue un mème de presque toutes les conférences et peu de gens sont surpris par des subtilités telles que:

1 + {}; // '1[object Object]' 1 + [[0]]; // '10' 1 + new Date; // '1Fri Feb 08 2019 00:32:57 GMT+0300 (,  )' 1 - new Date; // -1549616425060 ... 

Nous obtenons soudainement une TypeError, essayant d'ajouter deux apparemment NUMBERS:

 1 + 1n; // TypeError: Cannot mix BigInt and other types, use explicit conversions 

Et si l'expérience antérieure de l'implicitation n'a pas conduit à une rupture dans l'apprentissage de la langue, alors il y a une deuxième chance de décomposer et de jeter le manuel ECMA et d'entrer dans du Java.

De plus, le langage continue de «troll» pour les développeurs js:

 1n + '1'; // '11' 

Oh oui, n'oubliez pas l'opérateur unaire + :

 +1n; // TypeError: Cannot convert a BigInt value to a number Number(1n); // 1 

En bref, nous ne pouvons pas mélanger BigInt et Number dans les opérations. Par conséquent, il n'est pas recommandé d'utiliser des «grands entiers» si 2 ^ 53-1 ( MAX_SAFE_INTEGER ) est suffisant pour nos besoins.

Décision clé


Oui, c'était la décision principale de cette innovation. Si vous oubliez qu'il s'agit de JavaScript, alors tout est tellement logique: ces conversions implicites contribuent à la perte d'informations.

Lorsque nous ajoutons deux valeurs de types numériques différents (grands entiers et nombres à virgule flottante), la valeur mathématique du résultat peut être en dehors de leur plage de valeurs possibles. Par exemple, la valeur de l'expression (2n ** 53n + 1n) + 0,5 ne peut être représentée avec précision par aucun de ces types. Ce n'est plus un entier, mais un nombre réel, mais sa précision n'est plus garantie par le format float64 :

 2n ** 53n + 1n; // 9007199254740993n Number(2n ** 53n + 1n) + 0.5; // 9007199254740992 

Dans la plupart des langages dynamiques, où les types d'entiers et de flottants sont représentés, les premiers sont écrits comme 1 et les seconds sont écrits comme 1.0 . Ainsi, lors d'opérations arithmétiques sur la présence d'un séparateur décimal dans l'opérande, on peut conclure que la précision du flottant dans les calculs est acceptable. Mais JavaScript n'en fait pas partie et 1 est un flottant! Et cela signifie que le calcul de 2n ** 53n + 1 renverra le flotteur 2 ^ 53. Ce qui, à son tour, brise les fonctionnalités clés de BigInt :

 2 ** 53 === 2 ** 53 + 1; // true 

Eh bien, il n'y a aucune raison de parler de la mise en œuvre de la "tour numérique" , car vous ne pourrez pas prendre le numéro existant comme type de données numériques générales (pour la même raison).

Et pour éviter ce problème, la conversion implicite entre Number et BigInt dans les opérations a été interdite. Par conséquent, le «grand entier» ne peut pas être converti en toute sécurité dans une fonction JavaScript ou API Web, où le nombre habituel est attendu:

 Math.max(1n, 10n); // TypeError 

Vous devez sélectionner explicitement l'un des deux types à l'aide de Number () ou BigInt () .

De plus, pour les opérations avec des types mixtes, il existe une explication concernant une implémentation complexe ou une perte de performances, ce qui est assez courant pour les innovations de langage de compromis.

Bien sûr, cela s'applique aux conversions numériques implicites avec d'autres primitives:

 1 + true; // 2 1n + true; // TypeError 1 + null; // 1 1n + null; // TypeError 

Mais les concaténations suivantes (déjà) fonctionneront, car le résultat attendu est une chaîne:

 1n + [0]; // '10' 1n + {}; // '1[object Object]' 1n + (_ => 1); // '1_ => 1' 

Une autre exception est sous la forme d'opérateurs de comparaison (comme < , > et == ) entre Number et BigInt . Il n'y a également aucune perte de précision, car le résultat est un booléen.

Eh bien, si vous vous souvenez du nouveau type de données Symbol précédent, TypeError ne semble plus être un ajout aussi radical?

 Symbol() + 1; // TypeError: Cannot convert a Symbol value to a number 

Et oui, mais non. En effet, conceptuellement, le symbole n'est pas du tout un nombre, mais un tout - beaucoup:

  1. Il est très peu probable que le symbole tombe dans une telle situation. Cependant, c'est très suspect et TypeError est tout à fait approprié ici.
  2. Il est très probable et habituel que le «grand tout» dans les opérations se révèle être l'un des opérandes alors qu'il n'y a vraiment rien de mal.

L'opérateur unary + lève une exception en raison d'un problème de compatibilité avec asm.js , où Number est attendu. Le plus unaire ne peut pas fonctionner avec BigInt de la même manière que Number , car dans ce cas, le code asm.js précédent deviendra ambigu.

Offre alternative


Malgré la relative simplicité et la «propreté» de la mise en œuvre de BigInt , Axel Rauschmeyer souligne le manque d'innovation. À savoir, sa seule rétrocompatibilité partielle avec le numéro existant et les suivants:
Utilisez des nombres pour des nombres jusqu'à 53 bits. Utilisez des entiers si vous avez besoin de plus de bits
Comme alternative, il a proposé ce qui suit .

Laissez Number devenir le supertype pour les nouveaux Int et Double :

  • typeof 123.0 === 'nombre' , et Number.isDouble (123.0) === true
  • typeof 123 === 'nombre' , et Number.isInt (123) === true

Avec de nouvelles fonctions pour les conversions Number.asInt () et Number.asDouble () . Et, bien sûr, avec la surcharge de l'opérateur et les moulages nécessaires:

  • Int × Double = Double (cast)
  • Double × Int = Double (avec fonte)
  • Double × Double = Double
  • Int × Int = Int (tous les opérateurs sauf division)

Fait intéressant, dans la version simplifiée, cette phrase gère (au début) sans ajouter de nouveaux types à la langue. Au lieu de cela, la définition du type de nombre se développe: en plus de tous les nombres possibles à double précision 64 bits (IEEE 754-2008), nombre inclut désormais tous les entiers. Par conséquent, le «nombre inexact» 123,0 et le «nombre exact» 123 sont des numéros distincts du type de numéro unique.

Cela semble très familier et raisonnable. Cependant, il s'agit d'une sérieuse mise à niveau du numéro existant, qui est plus susceptible de «casser le Web» et ses outils:

  • Il y a une différence entre 1 et 1.0 , qui n'existait pas auparavant. Le code existant les utilise de manière interchangeable, ce qui, après la mise à niveau, peut être source de confusion (contrairement aux langues où cette différence était présente initialement).
  • Il y a un effet lorsque 1 === 1.0 (c'est censé être une mise à niveau), et en même temps, Number.isDouble (1)! == Number.isDouble (1.0) : encore une fois, c'est comme ça.
  • La «particularité» de l'égalité 2 ^ 53 et 2 ^ 53 + 1 disparaît, ce qui va casser le code qui en dépend.
  • Le même problème de compatibilité avec asm.js et plus.

Par conséquent, nous avons finalement une solution de compromis sous la forme d'un nouveau type de données distinct. Il convient de souligner qu’une autre option a également été envisagée et discutée .

Lorsque vous êtes assis sur deux chaises


En fait, le commentaire du comité commence par les mots:
Trouver un équilibre entre maintenir l'intuition de l'utilisateur et préserver la précision

D'une part, je voulais enfin ajouter quelque chose d '«exact» à la langue. Et d'autre part, pour maintenir son comportement déjà familier pour de nombreux développeurs.

C'est juste que cet "exact" ne peut pas être ajouté, car vous ne pouvez pas le casser: les mathématiques, l'ergonomie du langage, asm.js, la possibilité d'une nouvelle expansion du système de type , la productivité et, finalement, le web lui-même! Et vous ne pouvez pas tout casser en même temps, ce qui conduit au même.

Et vous ne pouvez pas briser l’intuition des utilisateurs de langues, qui, bien sûr, a également été vivement débattue . C'est vrai, ça a marché?

Source: https://habr.com/ru/post/fr439402/


All Articles