🚀 Active Patterns
Limitations du Pattern Matching
Nombre limité de patterns
Impossibilité de factoriser l'action de patterns avec leur propre guard
Pattern1 when Guard1 | Pattern2 when Guard2 -> do💥Pattern1 when Guard1 -> do | Pattern2 when Guard2 -> do😕
Patterns ne sont pas des citoyens de 1ère classe
Ex : une fonction ne peut pas renvoyer un pattern
→ Juste une sorte de sucre syntaxique
Patterns interagissent mal avec un style OOP
Origine des Active Patterns
🔗 Extensible pattern matching via a lightweight language extension
ℹ️ Publication de 2007 de Don Syme, Gregory Neverov, James Margetson
Intégré à F♯ 2.0 (2010)
💡 Idées
Permettre le pattern matching sur d'autres structures de données
Faire de ces nouveaux patterns des citoyens de 1ère classe
Syntaxe
Syntaxe générale : let (|Cases|) [arguments] valueToMatch = expression
Fonction avec un nom spécial défini dans une "banane"
(|...|)Ensemble de 1..N cases où ranger
valueToMatch
💡 Sorte de fonction factory d'un type union "anonyme", défini inline
Types
Il existe 4 types d'active patterns :
Pattern total simple
Pattern total multiple
Pattern partiel
Pattern paramétré
💡 Partiel et total indique la faisabilité du « rangement dans le(s) case(s) » de la valeur en entrée
Partiel : il n'existe pas toujours une case correspondante
Total : il existe forcément une case correspondante → pattern exhaustif
Active pattern total simple
A.k.a Single-case Total Pattern
Syntaxe : let (|Case|) [...parameters] value = Case [data] Usage : déconstruction en ligne
Autre exemple : extraction de la forme polaire d'un nombre complexe
Sans l'active pattern, c'est un autre style mais de lisibilité équivalente :
Active pattern total multiple
A.k.a Multiple-case Total Pattern
Syntaxe : let (|Case1|...|CaseN|) value = CaseI [dataI]
☝ Ne peut pas prendre de paramètre d’entrée❗
Active pattern partiel
Syntaxe : let (|Case|_|) value = Some Case | Some data | None
Renvoie type
'T optionsi Case comprend des données, sinonunit optionPattern matching est non exhaustif → il faut un cas par défaut
Exemple similaire, où les active patterns sont écrits la fonction Option.ofTuple :
💡 Pour bien se rendre du gain de lisibilité du code, il suffit d'écrire une version de + bas niveau de tryParseBoolean où l'on constate :
Imbrication des expressions
matchDifficultés à lire les lignes 6 et 7 du fait des doubles booléens (
true..false,true..true)
Active pattern partiel paramétré
Syntaxe : let (|Case|_|) ...arguments value = Some Case | Some data | None
Exemple 1 : année bissextile = multiple de 4 mais pas 100 sauf 400
Exemple 2 : expression régulière
Exemple : Couleur hexadécimale
Récapitulatif
Total multiple
let (|Case1|…|CaseN|) x
'T -> Choice<'U1, …, 'Un>
Total simple
let (|Case|) x
'T -> 'U
Partiel simple
let (|Case|_|) x
'T -> 'U option
... paramétré
let (|Case|_|) p1 … pN x
'P1 -> … -> 'Pn -> 'T -> 'U option
Comprendre un active pattern
Comprendre comment utiliser un active pattern... peut s'avérer un vrai jonglage intellectuel !
👉 Explications en utilisant les exemples précédents
Comprendre un active pattern total
Active pattern total ≃ Fonction factory d'un type union "anonyme"
Usage : idem pattern matching d'un type union normal
Comprendre un active pattern partiel
☝ Bien distinguer les éventuels paramètres des éventuelles données
Examiner la signature de l'active pattern : [...params ->] value -> 'U option
Les 1..N-1 paramètres = paramètres de l'active pattern
Son retour :
'U option→ données de type'U; si'U=unit→ pas de donnée
À l'usage : match value with Case [params] [data]
Case params≃ application partielle, donnant active pattern sans paramètreCaseWithParams data≃ déconstruction d'un case de type union
→ Exemples vus :
let (|Integer|_|) (s: string) : int optionUsage
match s with Integer i, aveci: intdonnée en sortie
let (|DivisibleBy|_|) (factor: int) (x: int) : unit optionUsage
match year with DivisibleBy 400, avec400le paramètrefactor
let (|Regexp|_|) (pattern: string) (value: string) : string list optionUsage
match s with Regexp "#([0-9...)" [ r; g; b ]Avec
"#([0-9...)"le paramètrepatternEt
[ r; g; b ]la liste en sortie décomposée en 3 chaînes
Exercice : fizz buzz
Ré-écrire ce fizz buzz en utilisant un active pattern DivisibleBy
Solution
💡 L'active pattern DivisibleBy 3 ne renvoie pas de donnée. C'est juste du sucre syntaxique équivalent de if y |> isDivisibleBy 3 . Dans un tel cas, F# 9 autorise à renvoyer directement le booléen plutôt que de devoir passer par le type Option :
Alternative
→ Les 2 solutions se valent. C'est une question de style / de goût personnel.
Cas d'utilisation des actives patterns
Factoriser une guard (cf. exercice précédent du fizz buzz)
Wrapper une méthode de la BCL (cf.
(|Regexp|_|)et ci-dessous)Améliorer l'expressivité, aider à comprendre la logique (cf. après)
Expressivité grâce aux actives patterns
Active pattern : citoyen de 1ère classe
Un active pattern ≃ fonction avec des métadonnées
Citoyen de 1ère classe :
Mis à jour
Ce contenu vous a-t-il été utile ?