Modèles actifs
modèles actifs vous permettent de définir des partitions nommées qui subdivisent les données d’entrée, afin que vous puissiez utiliser ces noms dans une expression de correspondance de modèle comme vous le feriez pour une union discriminatoire. Vous pouvez utiliser des modèles actifs pour décomposer les données de manière 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.
// Can use FSharp.Core.option<_>, FSharp.Core.voption<_> or bool to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression
Remarques
Dans la syntaxe précédente, les identificateurs sont des noms pour les partitions des données d’entrée représentées par arguments, ou, en d’autres termes, les noms des sous-ensembles de tous les valeurs des arguments. Il peut y avoir jusqu’à sept partitions dans une définition de modèle active. L’expression décrit la forme dans laquelle décomposer les données. Vous pouvez utiliser une définition de modèle active pour définir les règles permettant de déterminer quelles partitions nommées les valeurs données en tant qu’arguments appartiennent. Les symboles (| et |) sont appelés « banana clips » et la fonction créée par ce type de liaison let est appelée « module de reconnaissance actif ».
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 HSB.
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 façons d’utiliser des modèles actifs vous permettent de partitionner et de décomposer des données en seulement le formulaire approprié et d’effectuer les calculs appropriés sur les données appropriées dans le formulaire le plus pratique pour le calcul.
Les expressions de correspondance de modèles résultantes permettent d’écrire des données de manière pratique qui sont très lisibles, 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 une 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 utilisez un caractère générique (_) à la fin de la liste des modèles dans les "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 motif Carré et le motif Cube ne sont pas disjoints, car certains nombres sont à la fois des carrés et des cubes, tels que 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 est la suivante :
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 mis en correspondance, mais ils peuvent également prendre des arguments supplémentaires, auquel cas le nom modèle actif paramétré s’applique. Des arguments supplémentaires permettent de spécialiser un modèle général. Par exemple, les modèles actifs qui utilisent des expressions régulières pour analyser les chaînes incluent souvent l’expression régulière comme paramètre supplémentaire, comme dans le code suivant, qui utilise également le modèle actif partiel Integer
défini dans l’exemple de code précédent. Dans cet exemple, les chaînes qui utilisent des expressions régulières pour différents formats de date sont fournies pour personnaliser le modèle actif ParseRegex général. Le modèle actif Integer est utilisé pour convertir les 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
Type de retour pour les modèles actifs partiels
Les modèles actifs partiels retournent Some ()
pour indiquer une correspondance et None
sinon.
Considérez cette correspondance :
match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...
Le modèle actif partiel est le suivant :
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
Some ()
else
None
À compter de F# 9, ces modèles peuvent également retourner bool
:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)
Représentations de structures pour les modèles actifs partiels
Par défaut, si un modèle actif partiel retourne un option
, cela implique une allocation pour la valeur Some
en cas de correspondance réussie. Pour éviter cela, vous pouvez utiliser une option de valeur comme valeur de retour à l’aide 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 de struct n’est pas déduite de la simple modification du type de retour en ValueOption
. Pour plus d’informations, consultez RFC FS-1039.