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.