Unions

A.k.a. Discriminated Unions (DU)

Points clés

  • Terme exacte : « Union discriminée », Discriminated Union (DU)

  • Types Somme : représente un OU, un choix entre plusieurs Cases

    • Même principe que pour une enum mais généralisé

  • Chaque case doit avoir un Tag (a.k.a Label)

    • C'est le discriminant de l'union pour identifier le case

  • Chaque case peut contenir des données

type Billet =
    | Adulte                 // aucune donnée -> ≃ singleton stateless
    | Senior of int          // contient un 'int' (mais on ne sait pas ce que c'est)
    | Enfant of age: int     // contient un 'int' de nom 'age'
    | Famille of Billet list // contient une liste de billet
                             // type récursif -- pas besoin de 'rec'

Qualification des Tags

Les Tags peuvent être utilisés :

  • sans qualification → Adulte

  • sauf pour résoudre un conflit de noms ou par choix de nommage → Billet.Adulte

☝ On peut forcer l'usage avec qualification en décorant l'union de l'attribut RequireQualifiedAccess, essentiellement pour des raisons de nommage de l'union et de ses tags, pour que le code se lise sans ambiguïté.

Casse des Tags

Les Tags doivent être nommés en PascalCase ❗

💡 Depuis F# 7.0, la camelCase est possible si l'union est décorée avec RequireQualifiedAccess.

Champs nommés - Labelled Fields

Pratiques pour :

  • Ajouter un sens à un type primitif : → Dans l'exemple précédent, en ligne 4, le case Enfant contient un champ de type int qui est nommé age.

  • Distinguer deux champs du même type au sein d'un Tuple : → Exemple :

Notes :

  • Le nommage des champs est optionnel.

  • En tant que champ, on optera pour le PascalCase. Mais on peut aussi les voir en tant que paramètres, alors en camelCase.

  • Quand un case contient plusieurs champs, on peut n'en nommer que certains. → Mais je ne recommande pas cette dissymétrie.

Déclaration

Sur plusieurs lignes : 1 ligne / case → ☝ Ligne indentée et commençant par |

Sur une seule ligne -- si déclaration reste courte ❗ → 💡 Pas besoin du 1er |

Instanciation

Tagconstructeur → Fonction appelée avec les éventuelles données du case

Conflit de noms

Quand 2 unions ont des tags de même nom → Qualifier le tag avec le nom de l'union

Accès aux données internes

Uniquement via pattern matching Matching d'un type Union est exhaustif

Single-case union

Union avec un seul cas encapsulant un type (généralement primitif)

Assure type safety contrairement au simple type alias → Impossible de passer un CustomerId à une fonction attendant un OrderId 👍

Permet d'éviter Primitive Obsession à coût minime

Style "enum"

Tous les cases sont vides = dépourvus de données → ≠ enum .NET 📍Enums

L'instanciation et le pattern matching se font juste avec le tag → Le tag n'est plus une fonction mais une valeur (singleton)

Last updated

Was this helpful?