I. Avant-propos▲
Anders Hejlsberg, le concepteur de TypeScript, a annoncé il y a deux semaines que son équipe travaillait à l'implémentation des unions de types.
L'union de types est un concept assez simple à comprendre, mais étrangement peu répandu dans les langages typés. Voici la traduction de la spécification #805 rédigée par Ryan Cavanaugh dans sa version originale.
II. L'opérateur | sur les types▲
Ceci est un résumé des spécifications imaginées par Anders Hejlsberg.
II-A. Cas d'utilisation▲
Beaucoup de bibliothèques JavaScript acceptent des valeurs de plus d'un seul type. Par exemple, la propriété AJAX jsonp avec jQuery peut être soit false (i.e. de type boolean) ou une chaîne de caractères (type string). Les fichiers de définition TypeScript (.d.ts) doivent représenter cette propriété avec le type any, perdant ainsi la sécurité du typage.
De même, la configuration du service HTTP d'AngularJS (https://docs.angularjs.org/api/ng/service/$http#usage) possède des propriétés de type boolean, Cache, number ou encore Promise.
II-B. Solutions de contournement actuelles▲
Cette lacune peut souvent être contournée avec des surcharges de fonction, mais il n'y a pas d'équivalent pour les propriétés des objets, les contraintes sur les types, ou d'autres rôles concernant les types.
III. Présentation▲
III-A. Syntaxe▲
Le nouvel opérateur |, lorsqu'il est utilisé pour séparer deux types, produit une union de types représentant une valeur qui est de l'un des types en entrée.
Exemple :
interface Settings {
foo: number|string;
}
function setSetting(s: Settings) { /* ... */ }
setSettings({ foo: 42 }); // OK
setSettings({ foo: '100' }); // OK
setSettings({ foo: false }); // Error, false is not assignable to number|stringPlusieurs types peuvent être combinés de cette façon :
function process(n: string|HTMLElement|JQuery) { /* ... */ }
process('foo'); // OK
process($('div')); // OK
process(42); // ErrorN'importe quel type est un opérande valide pour l'opérateur |. Voici quelques exemples et comment ils seraient analysés :
var x: number|string[]; // x is a number or a string[]
var y: number[]|string[]; // y is a number[] or a string[]
var z: Array<number|string>; // z is an array of number|string
var t: F|typeof G|H; // t is an F, typeof G, or H
var u: () => string|number; // u is a function that returns a string|number
var v: { (): string; }|number; // v is a function that returns a string, or a numberNotez que les parenthèses n'étant pas nécessaires pour lever l'ambiguïté, elles ne sont pas acceptées.
III-B. Interprétation▲
La signification de A|B est un type qui est soit un A, soit un B. En particulier, c'est différent d'un type qui combinerait tous les membres de A et de B. Nous examinerons cela dans des exemples plus loin.
IV. Sémantique▲
IV-A. Notions de base▲
Quelques règles simples :
- identité : A|A est équivalent à A ;
- commutativité : A|B est équivalent à B|A ;
- associativité : (A|B)|C est équivalent à A|(B|C);
- effacement du sous-type : A|B est équivalent à A si B est un sous-type de A.
IV-B. Propriétés (attributs)▲
Le type A|B possède une propriété P de type X|Y si A possède une propriété P de type X et B possède une propriété P de type Y. Ces propriétés doivent soit être à la fois publiques, soit provenir du même site de déclaration (tel que spécifié dans les règles pour private/protected). Si l'une des propriétés est facultative, la propriété qui en résulte est également facultative.
Exemple :
interface Car {
weight: number;
gears: number;
type: string;
}
interface Bicycle {
weight: number;
gears: boolean;
size: string;
}
var transport: Car|Bicycle = /* ... */;
var w: number = transport.weight; // OK
var g = transport.gears; // OK, g is of type number|boolean
console.log(transport.type); // Error, transport does not have 'type' property
console.log((<Car>transport).type); // OKV. Appel et construction de signatures▲
Le type A|B a une signature d'appel F si A a une signature d'appel F et B a une signature d'appel F.
Exemple :
var t: string|boolean = /* ... */;
console.log(t.toString()); // OK (both string and boolean have a toString method)La même règle est appliquée pour construire signatures.
V-A. Signatures d'indice▲
Le type A|B a une signature d'indice [x: number]: T ou [x: string]: T si les deux A et B ont une signature d'indice de ce type.
VI. Réductibilité (assignability) et sous-typage▲
Nous décrivons ici la réductibilité ; le sous-typage est la même chose, sauf que « est réductible à » est remplacé par « est un sous-type de ».
Le type S est réductible au type T1|T2 si S est réductible à T1 ou si S est réductible à T2.
Exemple :
var x: string|number;
x = 'hello'; // OK, can assign a string to string|number
x = 42; // OK
x = { }; // Error, { } is not assignable to string or assignable to numberLe type S1|S2 est réductible au type T si les deux S1 et S2 sont réductibles à T.
Exemple :
var x: string|number = /* ... */;
var y: string = x; // Error, number is not assignable to string
var z: number = x; // Error, string is not assignable to numberEn combinant les règles, le type S1|S2 est réductible au type T1|T2 si S1 est réductible à T1 ou T2 et S2 est réductible à T1 ou T2. Plus généralement, tous les types sur la partie droite de la réduction doivent être réduits à au moins un type sur la partie gauche.
Exemple :
var x: string|number;
var y: string|number|boolean;
x = y; // Error, boolean is not assignable to string or number
y = x; // OK (both string and number are assignable to string|number)VII. Meilleur type commun▲
L'algorithme actuel du meilleur type commun (c.f. spécifications section 3.10) est seulement capable de produire un type déjà existant parmi les candidats, ou le type {}. Par exemple, le tableau [1, 2, "hello"] est de type {}[]. Avec la possibilité de représenter les unions de types, nous pouvons changer l'algorithme du meilleur type commun pour produire une union de types lorsqu'on est en présence d'un ensemble de candidats sans supertype.
Exemple :
class Animal { run(); }
class Dog extends Animal { woof(); }
class Cat extends Animal { meow(); }
class Rhino extends Animal { charge(); }
var x = [new Dog(), new Cat()];
// Current behavior: x is of type {}[]
// Proposed: x is of type Array<Dog|Cat>Notez que dans ce cas, le type Dog|Cat est structurellement équivalent à Animal par rapport à ses membres, mais il serait une erreur d'essayer d'attribuer un Rhino à x[0], car Rhino n'est pas réductible à Cat ou Dog.
Le meilleur type commun est utilisé pour plusieurs inférences réalisées par le langage. Dans les cas
- x || y,
- z ? x : y,
- z ? x : y et
- [x, y],
le type résultant sera X | Y (où X est le type de x et Y est le type de y). Pour l'instruction return dans une fonction et pour l'inférence du type générique, nous allons exiger l'existence d'un supertype entre les candidats.
Exemple :
// Error, no best common type among 'string' and 'number'
function fn() {
if(Math.random() > 0.5) {
return 'hello';
} else {
return 42;
}
}
// OK with type annotation
function fn(): string|number {
/* ... same as above ... */
}VIII. Prochaines étapes possibles▲
VIII-A. Combinaison des membres de types▲
D'autres scénarios nécessitent un type construit à partir de A et B ayant tous ses membres présents dans l'un ou l'autre des deux types, mais pas dans les deux. Au lieu d'ajouter une nouvelle syntaxe de type, nous pouvons représenter cela facilement en supprimant la restriction qui fait que les clauses extends peuvent ne pas référencer les paramètres de type de leur déclaration.
Exemple :
interface HasFoo<T> extends T {
foo: string;
}
interface Point {
x: number;
y: number;
}
var p: HasFoo<Point> = /* ... */;
console.log(p.foo); // OK
console.log(p.x.toString(); // OKVIII-B. Signification locale des unions de types▲
Pour les unions de types où un opérande est une primitive, nous avons pu détecter certains schémas syntaxiques et ajuster le type d'un identifiant dans les blocs conditionnels.
Exemple :
var p: string|Point = /* ... */;
if(typeof p === 'string') {
console.log(p); // OK, 'p' has type string in this block
} else {
console.log(p.x.toString()); // OK, 'p' has type Point in this block
}Cela pourrait également s'étendre à des vérifications d'appartenance :
interface Animal { run(); }
interface Dog extends Animal { woof(); }
interface Cat extends Animal { meow(); }
var x: Cat|Dog = /* ... */;
if(x.woof) {
// x is 'Dog' here
}
if(typeof x.meow !== 'undefined') {
// x is 'Cat' here
}IX. Remerciements▲
Je remercie l'équipe TypeScript de Microsoft pour m'avoir autorisé à publier cette traduction, ainsi que Claude Leloup pour la relecture orthographique de cet article.
Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. Commentez.




