Modèles actifs

Les modèles actifs vous permettent de définir des partitions nommées qui subdivisent les données d’entrée afin de pouvoir utiliser ces noms dans une expression de correspondance de modèle, comme vous le feriez pour une union différenciée. Vous pouvez utiliser des modèles actifs pour décomposer des données de façon personnalisée pour chaque partition.

Syntaxe

// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression

// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression

// Partial active pattern definition.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression

Notes

Dans la syntaxe précédente, les identificateurs sont des noms pour les partitions des données d’entrée représentées par des arguments ou, en d’autres termes, des noms pour les sous-ensembles de toutes les valeurs des arguments. Il peut y avoir jusqu’à sept partitions dans une définition de modèle active. L’expression décrit le formulaire dans lequel décomposer les données. Vous pouvez utiliser une définition de modèle actif pour définir les règles permettant de déterminer à laquelle des partitions nommées appartiennent les valeurs données en argument. Les symboles (| et |) sont appelés bananas clips, et la fonction créée par ce type de liaison let est appelée reconnaissance active.

Par exemple, considérez le modèle actif suivant avec un argument.

let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd

Vous pouvez utiliser le modèle actif dans une expression de correspondance de modèle, comme dans l’exemple suivant.

let TestNumber input =
   match input with
   | Even -> printfn "%d is even" input
   | Odd -> printfn "%d is odd" input

TestNumber 7
TestNumber 11
TestNumber 32

La sortie de ce programme est la suivante :

7 is odd
11 is odd
32 is even

Une autre utilisation des modèles actifs consiste à décomposer les types de données de plusieurs façons, par exemple lorsque les mêmes données sous-jacentes ont différentes représentations possibles. Par exemple, un objet Color peut être décomposé en une représentation RVB ou une représentation TSL.

open System.Drawing

let (|RGB|) (col : System.Drawing.Color) =
     ( col.R, col.G, col.B )

let (|HSB|) (col : System.Drawing.Color) =
   ( col.GetHue(), col.GetSaturation(), col.GetBrightness() )

let printRGB (col: System.Drawing.Color) =
   match col with
   | RGB(r, g, b) -> printfn " Red: %d Green: %d Blue: %d" r g b

let printHSB (col: System.Drawing.Color) =
   match col with
   | HSB(h, s, b) -> printfn " Hue: %f Saturation: %f Brightness: %f" h s b

let printAll col colorString =
  printfn "%s" colorString
  printRGB col
  printHSB col

printAll Color.Red "Red"
printAll Color.Black "Black"
printAll Color.White "White"
printAll Color.Gray "Gray"
printAll Color.BlanchedAlmond "BlanchedAlmond"

La sortie du programme ci-dessus est la suivante :

Red
 Red: 255 Green: 0 Blue: 0
 Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
 Red: 0 Green: 0 Blue: 0
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
 Red: 255 Green: 255 Blue: 255
 Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
 Red: 128 Green: 128 Blue: 128
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
 Red: 255 Green: 235 Blue: 205
 Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961

En combinaison, ces deux manières d'utiliser les modèles actifs vous permettent de partitionner et de décomposer les données sous la forme appropriée et d'effectuer les calculs appropriés sur les données adéquates sous la forme la plus pratique pour le calcul.

Les expressions de correspondance de modèles résultantes permettent d’écrire des données de manière pratique de manière très lisible, ce qui simplifie considérablement le code de branchement et d’analyse des données potentiellement complexes.

Modèles actifs partiels

Parfois, vous devez partitionner uniquement une partie de l’espace d’entrée. Dans ce cas, vous écrivez un ensemble de modèles partiels dont chacun correspond à certaines entrées, mais qui ne correspondent pas à d’autres entrées. Les modèles actifs qui ne produisent pas toujours de valeur sont appelés modèles actifs partiels; ils ont une valeur de retour qui est un type d’option. Pour définir un modèle actif partiel, vous devez utiliser un caractère générique (_) à la fin de la liste des modèles, à l’intérieur des banana clips. Le code suivant illustre l’utilisation d’un modèle actif partiel.

let (|Integer|_|) (str: string) =
   let mutable intvalue = 0
   if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
   else None

let (|Float|_|) (str: string) =
   let mutable floatvalue = 0.0
   if System.Double.TryParse(str, &floatvalue) then Some(floatvalue)
   else None

let parseNumeric str =
   match str with
   | Integer i -> printfn "%d : Integer" i
   | Float f -> printfn "%f : Floating point" f
   | _ -> printfn "%s : Not matched." str

parseNumeric "1.1"
parseNumeric "0"
parseNumeric "0.0"
parseNumeric "10"
parseNumeric "Something else"

La sortie de l’exemple précédent est la suivante :

1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.

Lorsque vous utilisez des modèles actifs partiels, les choix individuels peuvent parfois être disjoints ou mutuellement exclusifs, mais ils n’ont pas besoin d’être. Dans l’exemple suivant, le modèle Carré et le modèle Cube ne sont pas disjoints, car certains nombres sont à la fois des carrés et des cubes, comme par exemple 64. Le programme suivant utilise le modèle AND pour combiner les modèles Carré et Cube. Il imprime tous les entiers jusqu’à 1 000 qui sont à la fois des carrés et des cubes, ainsi que ceux qui ne sont que des cubes.

let err = 1.e-10

let isNearlyIntegral (x:float) = abs (x - round(x)) < err

let (|Square|_|) (x : int) =
  if isNearlyIntegral (sqrt (float x)) then Some(x)
  else None

let (|Cube|_|) (x : int) =
  if isNearlyIntegral ((float x) ** ( 1.0 / 3.0)) then Some(x)
  else None

let findSquareCubes x =
   match x with
   | Cube x & Square _ -> printfn "%d is a cube and a square" x
   | Cube x -> printfn "%d is a cube" x
   | _ -> ()
   

[ 1 .. 1000 ] |> List.iter (fun elem -> findSquareCubes elem)

La sortie se présente comme suit :

1 is a cube and a square
8 is a cube
27 is a cube
64 is a cube and a square
125 is a cube
216 is a cube
343 is a cube
512 is a cube
729 is a cube and a square
1000 is a cube

Modèles actifs paramétrés

Les modèles actifs prennent toujours au moins un argument pour l'élément recherché, mais ils peuvent également prendre des arguments supplémentaires, auquel cas le modèle actif paramétré par le nom s'applique. Des arguments supplémentaires permettent de spécialiser un modèle général. Par exemple, les motifs actifs qui utilisent des expressions régulières pour analyser les chaînes de caractères incluent souvent l'expression régulière en tant que paramètre supplémentaire, comme dans le code suivant, qui utilise également le motif actif partiel Integer défini dans l'exemple de code précédent. Dans cet exemple, des chaînes utilisant des expressions régulières pour différents formats de date sont données pour personnaliser le motif actif général ParseRegex. Le motif actif Integer est utilisé pour convertir les chaînes de caractères correspondantes en nombres entiers qui peuvent être transmis au constructeur DateTime.es chaînes correspondantes en entiers qui peuvent être passés au constructeur DateTime.

open System.Text.RegularExpressions

// ParseRegex parses a regular expression and returns a list of the strings that match each group in
// the regular expression.
// List.tail is called to eliminate the first element in the list, which is the full matched expression,
// since only the matches for each group are wanted.
let (|ParseRegex|_|) regex str =
   let m = Regex(regex).Match(str)
   if m.Success
   then Some (List.tail [ for x in m.Groups -> x.Value ])
   else None

// Three different date formats are demonstrated here. The first matches two-
// digit dates and the second matches full dates. This code assumes that if a two-digit
// date is provided, it is an abbreviation, not a year in the first century.
let parseDate str =
   match str with
   | ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
          -> new System.DateTime(y + 2000, m, d)
   | ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
          -> new System.DateTime(y, m, d)
   | ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
          -> new System.DateTime(y, m, d)
   | _ -> new System.DateTime()

let dt1 = parseDate "12/22/08"
let dt2 = parseDate "1/1/2009"
let dt3 = parseDate "2008-1-15"
let dt4 = parseDate "1995-12-28"

printfn "%s %s %s %s" (dt1.ToString()) (dt2.ToString()) (dt3.ToString()) (dt4.ToString())

La sortie du code précédent est la suivante :

12/22/2008 12:00:00 AM 1/1/2009 12:00:00 AM 1/15/2008 12:00:00 AM 12/28/1995 12:00:00 AM

Les modèles actifs ne sont pas limités uniquement aux expressions de correspondance de modèles, vous pouvez également les utiliser sur les liaisons let.

let (|Default|) onNone value =
    match value with
    | None -> onNone
    | Some e -> e

let greet (Default "random citizen" name) =
    printfn "Hello, %s!" name

greet None
greet (Some "George")

La sortie du code précédent est la suivante :

Hello, random citizen!
Hello, George!

Notez toutefois que seuls les modèles actifs à cas unique peuvent être paramétrés.

// A single-case partial active pattern can be parameterized
let (| Foo|_|) s x = if x = s then Some Foo else None
// A multi-case active patterns cannot be parameterized
// let (| Even|Odd|Special |) (s: int) (x: int) = if x = s then Special elif x % 2 = 0 then Even else Odd

Représentations struct pour les modèles actifs partiels

Par défaut, les modèles actifs partiels retournent une valeur option, ce qui implique une allocation pour la valeur Some sur une correspondance réussie. Vous pouvez également utiliser une option valeur comme valeur de retour via l’utilisation de l’attribut Struct :

open System

[<return: Struct>]
let (|Int|_|) str =
   match Int32.TryParse(str) with
   | (true, n) -> ValueSome n
   | _ -> ValueNone

L'attribut doit être spécifié, car l'utilisation d'un retour struct n'est pas inférée de la simple modification du type de retour en ValueOption. Pour plus d’informations, consultez RFC FS-1039.

Voir aussi