Emulation de typage nominal en TypeScript
Peut-être ce titre sonne-t-il un peu "Les chevaliers de l'an mille au lac de Paladru" (vous avez la ref ?), alors laissez-moi vous guider.
Un système de types est dit nominal lorsqu'un type est défini par son nom. Deux types peuvent être identiques dans les faits (mêmes données, même comportement), s'ils ont des noms différents, le compilateur les considérera comme différents.
Cela s'oppose au typage structurel, dans lequel un type est défini par sa structure. On parle de duck typing (très sérieusement), parce que si ça vole et que ça fait "coin coin" comme un canard, c'est que c'est un canard 🦆.
Java est un système de types nominal tandis que TypeScript est un système de types structurel.
Le choix à l'origine de TypeScript a de nombreux avantages, qui sont largement documentés sur le Web. Mais quelles en sont les limites ?
Oui, ce choix peut être limitant dans le cas de la programmation défensive, qui consiste notamment à encapsuler les primitives dans des abstractions ad hoc. Cela nous incite à faire émerger et nommer des notions importantes du métier (#ddd), et cela nous incite à effectuer dès la création de ces objets toutes les validations nécessaires.
Par exemple, lorsqu'on parle de fractions, il est intéressant de créer les types suivants :
type Numerator = { value: number };
type Denominator = { value: number };
Ah oui, sauf que… si le typage est structurel, ces types sont identiques 😢 Le risque est donc bien réel d'intervertir numérateur et dénominateur au moment de créer une fraction.
Que faire, alors ?
Eh bien nous pouvons toujours émuler un typage nominal. Pour cela, nous allons utiliser :
-
Un symbole (
DENOMINATOR_SYMBOL
), que nous aurons soin de ne pas exporter ; -
Un constructeur (
createDenominator
), fonction qui accède au symbole et l'utilise comme clef dans l'objet qu'elle crée. C'est bien sûr l'occasion d'effectuer les contrôles qui s'imposent (vérifier que la valeur n'est pas nulle).
Ainsi :
-
Les types
Numerator
etDenominator
ne sont plus intervertibles ; -
Créer un objet
Denominator
n'est possible qu'en passant par la fonctioncreateDenominator
, interdisant de ce fait des dénominateurs nuls.
Elle est pas belle, la vie ? 🏝️🍸
Le code complet est disponible sur GitHub : https://github.com/mathieueveillard/nominal-typing-emulation