Formation F#
  • Intro
  • Bases
    • Le F♯, c'est quoi ?
    • Syntaxe
    • Premiers concepts
    • 🍔 Quiz
  • Fonctions
    • Signature
    • Fonctions
    • Fonctions standard
    • Opérateurs
    • Fonctions : compléments
    • 🍔 Quiz
    • 📜 Récap’
  • Types composites
    • Généralités
    • Tuples
    • Records
    • Unions
    • Enums
    • Records anonymes
    • Types valeur
    • 🍔 Quiz
  • Types : Compléments
    • Type unit
    • Génériques
    • Types flexibles
    • Unités de mesure
    • Conversion
    • Exceptions F#
  • Pattern matching
    • Patterns
    • Match expression
    • 🚀 Active Patterns
    • 📜 Récap’
  • Collections
    • Vue d'ensemble
    • Types
    • Fonctions génériques
    • Fonctions spécifiques
    • 🍔 Quiz
    • 📜 Récap’
  • Programmation asynchrone
    • Workflow asynchrone
    • Interop avec la TPL .NET
    • 📜 Récap’
  • Types monadiques
    • Type Option
    • Type Result
    • Smart constructor
    • 🚀 Computation expression (CE)
    • 🚀 CE - Fondements théoriques
    • 📜 Récap’
  • Module & namespace
    • Vue d'ensemble
    • Namespace
    • Module
    • 🍔 Quiz
    • 📜 Récap’
  • Orienté-objet
    • Introduction
    • Membres
    • Extensions de type
    • Classe, structure
    • Interface
    • Expression objet
    • Recommandations
  • 🦚 Aller plus loin
Propulsé par GitBook
Sur cette page
  • Points clés
  • Qualification des Tags
  • Casse des Tags
  • Champs nommés - Labelled Fields
  • Déclaration
  • Instanciation
  • Conflit de noms
  • Accès aux données internes
  • Single-case union
  • Style "enum"

Cet article vous a-t-il été utile ?

Modifier sur GitHub
  1. Types composites

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 :

type ComplexNumber =
    | Cartesian of Real: float * Imaginary: float
    | Polar of Magnitude: float * Phase: float

☝ 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 |

open System

type IntOrBool =
    | Int32 of Int32                        // 💡 Tag de même nom que ses données
    | Boolean of Boolean

type OrderId = OrderId of int               // 👈 Single-case union
                                            // 💡 Tag de même nom que l'union parent
type Found<'T> = Found of 'T | NotFound     // 💡 Type générique

Instanciation

Tag ≃ constructeur → Fonction appelée avec les éventuelles données du case

type Shape =
    | Circle of radius: int
    | Rectangle of width: int * height: int

let circle = Circle 12          // Type: 'Shape', Valeur: 'Circle 12'
let rect = Rectangle (4, 3)     // Type: 'Shape', Valeur: 'Rectangle (4, 3)'

// Utilisation du nom des champs
let rec2 = Rectangle (height = 4, width = 6)

let circles = [1..4] |> List.map Circle     // 👈 Tag employé comme fonction

Conflit de noms

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

type Shape =
    | Circle of radius: int
    | Rectangle of width: int * height: int

type Draw = Line | Circle       // 'Circle' sera en conflit avec le tag de 'Shape'

let draw = Circle              // Type='Draw' (type le + proche) -- ⚠️ à éviter car ambigu

// Tags qualifiés par leur type union
let shape = Shape.Circle 12
let draw' = Draw.Circle

Accès aux données internes

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

type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float

let area shape =
    match shape with
    | Circle r -> Math.PI * r * r   // 💡 Même syntaxe que instanciation
    | Rectangle (w, h) -> w * h

let isFlat = function 
    | Circle 0.                     // 💡 Constant pattern
    | Rectangle (0., _)
    | Rectangle (_, 0.) -> true     // 💡 OR pattern
    | Circle _
    | Rectangle _ -> false

Single-case union

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

type CustomerId = CustomerId of int
type OrderId = OrderId of int

let fetchOrder (OrderId orderId) =    // 💡 Déconstruction directe sans 'match'
    ...

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

type Answer = Yes | No | Maybe
let answer = Yes

let print answer =
    match answer with
    | Yes   -> printfn "Oui"
    | No    -> printfn "Non"
    | Maybe -> printfn "Peut-être"
PrécédentRecordsSuivantEnums

Dernière mise à jour il y a 2 ans

Cet article vous a-t-il été utile ?

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

singleton