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 type est un concept assez simple � comprendre mais �trangement peu r�pandu dans les langages typ�s. Voici la traduction de la sp�cification #805 :
L'op�rateur | pour les types
Ceci est un r�sum� des sp�cifications imagin�es par Anders Hejlsberg.
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 soit boolean ou Cache ou number ou encore Promise.
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.
Introduction
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 :
Plusieurs types peuvent �tre combin�s de cette fa�on :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7 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|string
N'importe quel type est un op�rande valide pour l'op�rateur |. Quelques exemples et comment ils seraient analys�s :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4 function process(n: string|HTMLElement|JQuery) { /* ... */ } process('foo'); // OK process($('div')); // OK process(42); // Error
Notez que les parenth�ses ne sont pas n�cessaires pour lever l'ambigu�t�, de sorte qu'elles ne sont pas accept�es.
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6 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 number
Interpr�tation
La signification de A|B est un type qui est soit un A ou un B . En particulier, c'est diff�rent d'un type qui combinerait tous les membres de A et de B. Nous examinerons ceci dans des exemples plus loin.
S�mantique
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
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 �tre soit � la fois publiques, ou doivent 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� est facultative, la propri�t� qui en r�sulte est �galement facultative.
Exemple :
Appel et construction de signatures
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 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); // OK
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 :
La m�me r�gle est appliqu�e pour construire signatures.
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2 var t: string|boolean = /* ... */; console.log(t.toString()); // OK (both string and boolean have a toString method)
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.
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 :
Le type S1|S2 est r�ductible au type T si les deux S1 et S2 sont r�ductibles � T.
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4 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 number
Exemple :
En 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.
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3 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 number
Exemple :
Meilleur type commun
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4 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)
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 :
Notez que dans ce cas, le type Dog|Cat est structurellement �quivalent � Animal en termes de ses membres, mais il ce serait une erreur d'essayer d'attribuer un Rhino � x[0] , car Rhino n'est pas r�ductible � Cat ou Dog.
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7 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>
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 les instructions return dans une fonction et l'inf�rence du type g�n�rique, nous allons exiger de l'existence d'un supertype entre les candidats.
Exemple :
Prochaines �tapes possibles
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12 // 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 ... */ }
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 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 :
Signification locale des unions de types
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10 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(); // OK
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 :
Cela pourrait �galement s'�tendre � des v�rifications d'appartenance :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6 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 }
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10 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 }
Partager