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
  • Généralités
  • Wildcard Pattern
  • Constant Pattern
  • Identifier Pattern
  • Variable Pattern
  • Champs nommés de cases d'union
  • Alias Pattern
  • OR et AND Patterns
  • Parenthesized Pattern
  • Construction Patterns
  • Cons et List Patterns
  • Array Pattern
  • Tuple Pattern
  • Record Pattern
  • Type Test Pattern
  • Bloc try/with
  • Boxing

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

Modifier sur GitHub
  1. Pattern matching

Patterns

PrécédentExceptions F#SuivantMatch expression

Dernière mise à jour il y a 2 ans

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

Généralités

Patterns = règles pour détecter la structure de données en entrée

Utilisés abondamment en F♯

  • Dans match expression, let binding de valeurs et de paramètres de fonctions

  • Très pratiques pour manipuler les types algébriques F♯ (tuple, record, union)

  • Composables : supporte plusieurs niveaux d'imbrication

  • Assemblables par ET/OU logiques

  • Supporte les littéraux : 1.0, "test"...

Wildcard Pattern

Représenté par _, seul ou combiné avec un autre pattern

Toujours vrai → A placer en dernier dans une match expression

Toujours chercher en 1er à traiter exhaustivement/explicitement tous les cas Quand impossible, utiliser alors le _

match option with
| Some 1 -> ...
| _ -> ...              // ⚠️ Non exhaustif

match option with
| Some 1 -> ...
| Some _ | None -> ...  // 👌 \+ exhaustif

Constant Pattern

Détecte constantes, null et littéraux de nombre, char, string, enum

[<Literal>]
let Three = 3   // Constante

let is123 num = // int -> bool
    match num with
    | 1 | 2 | Three -> true
    | _ -> false

☝ Notes :

  • Le pattern de Three est aussi classé en tant que Identifier Pattern 📍

  • Pour le matching de null, on parle aussi de Null Pattern

Identifier Pattern

Détecte les cases d'un type union ainsi que leur éventuel contenu

type PersonName =
    | FirstOnly of string
    | LastOnly  of string
    | FirstLast of string * string

let classify personName =
    match personName with
    | FirstOnly _ -> "First name only"
    | LastOnly  _ -> "Last name only"
    | FirstLast _ -> "First and last names"

Variable Pattern

Assigne la valeur détectée à une "variable" pour l'utiliser ensuite

Exemple : variables firstName et lastName ci-dessous

type PersonName =
    | FirstOnly of string
    | LastOnly  of string
    | FirstLast of string * string

let confirm personName =
    match personName with
    | FirstOnly (firstName) -> printf "May I call you %s?" firstName
    | LastOnly  (lastName) -> printf "Are you Mr. or Ms. %s?" lastName
    | FirstLast (firstName, lastName) -> printf "Are you %s %s?" firstName lastName
let elementsAreEqualKo tuple =
    match tuple with
    | (x,x) -> true  // 💥 Error FS0038: 'x' est lié à deux reprises dans ce modèle
    | (_,_) -> false

Solutions : utiliser 2 variables puis vérifier l'égalité

// 1. Guard clause📍
let elementsAreEqualOk = function
    | (x,y) when x = y -> true
    | (_,_) -> false

// 2. Déconstruction
let elementsAreEqualOk' (x, y) = x = y

Champs nommés de cases d'union

Plusieurs possibilités :

  1. Pattern "anonyme" du tuple complet → Il faut déconstruire tous les champs du tuple. → On utilise la virgule , pour séparer ces champs.

  2. Pattern d'un seul champ par son nom → Field = value

type Shape =
    | Rectangle of Height: int * Width: int
    | Circle of Radius: int

let describe shape =
    match shape with
    | Rectangle (0, _)                                              // (1)
    | Rectangle (Height = 0)            -> "Flat rectangle"         // (2)
    | Rectangle (Width = w; Height = h) -> $"Rectangle {w} × {h}"   // (3)
    | Circle radius                     -> $"Circle ∅ {2*radius}"

Alias Pattern

as permet de nommer un élément dont le contenu est déconstruit

let (x, y) as coordinate = (1, 2)
printfn "%i %i %A" x y coordinate  // 1 2 (1, 2)

💡 Marche aussi dans les fonctions :

type Person = { Name: string; Age: int }

let acceptMajorOnly ({ Age = age } as person) =
    if age < 18 then None else Some person

OR et AND Patterns

Permettent de combiner deux patterns (nommés P1 et P2 ci-après)

  • P1 | P2 → P1 ou P2. Ex : Rectangle (0, _) | Rectangle (_, 0)

  • P1 & P2 → P1 et P2. Utilisé surtout avec 🚀 Active Patterns📍

💡 On peut utiliser la même variable (name ci-dessous) dans 2 patterns :

type Upload = { Filename: string; Title: string option }

let titleOrFile ({ Title = Some name } | { Filename = name }) = name

titleOrFile { Filename = "Report.docx"; Title = None }            // Report.docx
titleOrFile { Filename = "Report.docx"; Title = Some "Report+" }  // "Report+"

Parenthesized Pattern

Usage des parenthèses () pour grouper des patterns, pour gérer la précédence

type Shape = Circle of Radius: int | Square of Side: int

let countFlatShapes shapes =
    let rec loop rest count =
        match rest with
        | (Square (Side = 0) | (Circle (Radius = 0))) :: tail -> loop tail (count + 1) // ①
        | _ :: tail -> loop tail count
        | [] -> count
    loop shapes 0

☝ Note : la ligne ① ne compilerait sans faire () :: tail

💡 Essayer de s'en passer quand c'est possible

let countFlatShapes shapes =
    let rec loop rest count =
        match rest with
        | Circle (Radius = 0) :: tail
        | Square (Side = 0) :: tail
          -> loop tail (count + 1)
        // [...]

Construction Patterns

Reprennent syntaxe de construction d'un type pour le déconstruire

  • Cons et List Patterns

  • Array Pattern

  • Tuple Pattern

  • Record Pattern

Cons et List Patterns

≃ Inverses de 2 types de construction d'une liste, avec même syntaxe

Cons Pattern : head :: tail → décompose une liste (avec >= 1 élément) en :

  • Head : 1er élément

  • Tail : autre liste avec le reste des éléments - peut être vide

List Pattern : [items] → décompose une liste en 0..N éléments

  • [] : liste vide

  • [x] : liste avec 1 élément mis dans la variable x

  • [x; y] : liste avec 2 éléments mis dans les variables x et y

  • [_; _] : liste avec 2 éléments ignorés

💡 x :: [] ≡ [x], x :: y :: [] ≡ [x; y]...

La match expression par défaut combine les 2 patterns : → Une liste est soit vide [], soit composée d'un item et du reste : head :: tail

Les fonctions récursives parcourant une liste utilise le pattern [] pour stopper la récursion :

let rec printList l =
    match l with
    | head :: tail ->
        printf "%d " head
        printList tail     // Récursion sur le reste
    | [] -> printfn ""     // Fin de récursion : liste parcourue entièrement

Array Pattern

Syntaxe: [| items |] pour 0..N items entre ;

let length vector =
    match vector with
    | [| x |] -> x
    | [| x; y |] -> sqrt (x*x + y*y)
    | [| x; y; z |] -> sqrt (x*x + y*y + z*z)
    | _ -> invalidArg (nameof vector) $"Vector with more than 4 dimensions not supported"

☝ Il n'existe pas de pattern pour les séquences, vu qu'elles sont "lazy".

Tuple Pattern

Syntaxe : items ou (items) pour 2..N items entre ,

💡 Pratique pour pattern matcher plusieurs valeurs en même temps

type Color = Red | Blue
type Style = Background | Text

let css color style =
    match color, style with
    | Red, Background -> "background-color: red"
    | Red, Text -> "color: red"
    | Blue, Background -> "background-color: blue"
    | Blue, Text -> "color: blue"

Record Pattern

Syntaxe : { Fields } pour 1..N Field = variable entre ;

  • Pas obligé de spécifier tous les champs du Record

  • En cas d'ambiguïté, qualifier le champ : Record.Field

💡 Marche aussi pour les paramètres d'une fonction :

type Person = { Name: string; Age: int }

let displayMajority { Age = age; Name = name } =
    if age >= 18
    then printfn "%s is major" name
    else printfn "%s is minor" name

let john = { Name = "John"; Age = 25 }
displayMajority john // John is major
type Person = { Name: string; Age: int }

let john = { Name = "John"; Age = 25 }
let { Name = name } = john  // 👌 val name : string = "John"

let john' = {| john with Civility = "Mister" |}
let {| Name = name' |} = john'  // 💥

Type Test Pattern

Syntaxe : my-object :? sub-type

Renvoie un bool → is C♯ (my-object is sub-type)

Usage : avec une hiérarchie de types

open System.Windows.Forms

let RegisterControl (control: Control) =
    match control with
    | :? Button as button -> button.Text <- "Registered."
    | :? CheckBox as checkbox -> checkbox.Text <- "Registered."
    | :? Windows -> invalidArg (nameof control) "Window cannot be registered"
    | _ -> ()
type Car = interface end

type Mercedes() =
    interface Car

let merc = Mercedes ()

merc :? Mercedes        // true with warning FS0067: Ce test de type ... conviendra toujours.
box merc :? Mercedes    // true
merc :? Car             // error FS0193: Incompatibilité de contrainte de type.
box merc :? Car         // true

Bloc try/with

On rencontre fréquemment ce pattern dans les blocs try/with :

try
    printfn "Difference: %i" (42 / 0)
with
| :? DivideByZeroException as x -> 
    printfn "Fail! %s" x.Message
| :? TimeoutException -> 
    printfn "Fail! Took too long"

Boxing

Le Type Test Pattern ne marche qu'avec des types références.

→ Pour un type valeur ou inconnu, il faut le convertir en objet (a.k.a boxing)

let isIntKo = function :? int -> true | _ -> false
// 💥 Error FS0008: test de type au moment de l'exécution du type 'a en int...

let isInt x =
    match box x with
    | :? int -> true
    | _ -> false

On ne peut pas lier à plusieurs reprises vers la même variable

Pattern de plusieurs champs par leur nom → F1 = v1; F2 = v2 Ne pas confondre avec l'option 1 : → Le délimiteur est ici le point-virgule ; et non la virgule , !

Les parenthèses compliquent la lecture

Rappel : il n'y a pas de pattern pour les Records anonymes !

Remarque : l'opérateur :? n'est pas conçu pour vérifier des évidences car il adopte alors un comportement déroutant : (Exemple tiré de cette )

⚠️
⚠️
⚠️
⚠️
⚠️
☝️
question stackoverflow