Modelli attivi
Modelli attivi consentono di definire partizioni denominate che suddividono i dati di input, in modo da poter usare questi nomi in un'espressione di corrispondenza dei modelli esattamente come si farebbe per un'unione discriminata. È possibile usare modelli attivi per scomporre i dati in modo personalizzato per ogni partizione.
Sintassi
// 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
Osservazioni
Nella sintassi precedente gli identificatori sono nomi per le partizioni dei dati di input rappresentati da argomenti o, in altre parole, nomi per subset del set di tutti i valori degli argomenti. In una definizione di modello attivo possono essere presenti fino a sette partizioni. L'espressione descrive il modulo in cui scomporre i dati. È possibile usare una definizione di criterio attivo per definire le regole per determinare a quale delle partizioni denominate appartengono i valori specificati come argomenti. I simboli (| e |) vengono definiti clip banana e la funzione creata da questo tipo di associazione let è denominata riconoscimento attivo.
Ad esempio, si consideri il seguente modello attivo con un argomento.
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
È possibile usare il pattern attivo in un'espressione di pattern matching, come nell'esempio seguente.
let TestNumber input =
match input with
| Even -> printfn "%d is even" input
| Odd -> printfn "%d is odd" input
TestNumber 7
TestNumber 11
TestNumber 32
L'output di questo programma è il seguente:
7 is odd
11 is odd
32 is even
Un altro uso dei modelli attivi consiste nello scomporre i tipi di dati in diversi modi, ad esempio quando gli stessi dati sottostanti hanno varie rappresentazioni possibili. Ad esempio, un oggetto Color
può essere scomposto in una rappresentazione RGB o in una rappresentazione 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"
L'output del programma precedente è il seguente:
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
In combinazione, questi due modi di usare i modelli attivi consentono di partizionare e scomporre i dati nel formato appropriato ed eseguire i calcoli appropriati sui dati appropriati nel formato più conveniente per il calcolo.
Le espressioni di corrispondenza di modelli risultanti consentono che i dati possano essere scritti in modo semplice e leggibile, semplificando notevolmente il codice di diramazione e analisi dei dati potenzialmente complesso.
Modelli attivi parziali
In alcuni casi, è necessario partizionare solo parte dello spazio di input. In tal caso, si scrive un set di modelli parziali ognuno dei quali corrisponde ad alcuni input, ma non corrisponde ad altri input. I modelli attivi che non sempre producono un valore vengono chiamati modelli attivi parziali; hanno un valore restituito che è un tipo di opzione. Per definire un modello attivo parziale, si usa un carattere jolly (_) alla fine dell'elenco di modelli all'interno delle clip banana. Il codice seguente illustra l'uso di un modello attivo parziale.
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"
L'output dell'esempio precedente è il seguente:
1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.
Quando si usano modelli attivi parziali, a volte le singole scelte possono essere disgiunte o mutuamente esclusive, ma non necessariamente devono esserlo. Nell'esempio seguente, il modello Square e il modello Cube non sono disgiunti, perché alcuni numeri sono sia quadrati che cubi, ad esempio 64. Il programma seguente usa il modello AND per combinare i modelli Square e Cube. Stampa tutti i numeri interi fino a 1000 che sono sia quadrati che cubi, nonché quelli che sono solo cubi.
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)
L'output è il seguente:
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
Modelli attivi con parametri
I modelli attivi accettano sempre almeno un argomento per l'elemento corrispondente, ma possono accettare anche argomenti aggiuntivi, nel qual caso si applica il nome modello attivo con parametri. Gli argomenti aggiuntivi consentono la specializzazione di un modello generale. Ad esempio, i modelli attivi che usano espressioni regolari per analizzare le stringhe spesso includono l'espressione regolare come parametro aggiuntivo, come nel codice seguente, che usa anche il modello attivo parziale Integer
definito nell'esempio di codice precedente. In questo esempio, le stringhe che usano espressioni regolari per vari formati di data vengono fornite per personalizzare il modello attivo parseRegex generale. Il modello attivo Integer viene usato per convertire le stringhe corrispondenti in numeri interi che possono essere passati al costruttore 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())
L'output del codice precedente è il seguente:
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
I modelli attivi non sono limitati solo alle espressioni di pattern matching, puoi usarli anche nelle dichiarazioni 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")
L'output del codice precedente è il seguente:
Hello, random citizen!
Hello, George!
Si noti tuttavia che è possibile parametrizzare solo modelli attivi a singolo caso.
// 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
Tipo restituito per modelli attivi parziali
I modelli attivi parziali restituiscono Some ()
per indicare una corrispondenza e None
in caso contrario.
Si consideri questa corrispondenza:
match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...
Il modello attivo parziale per questo sarebbe:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
Some ()
else
None
A partire da F# 9, questi modelli possono restituire anche bool
:
let (|CaseInsensitive|_|) (pattern: string) (value: string) =
String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)
Rappresentazioni di struct per modelli attivi parziali
Per impostazione predefinita, se un modello attivo parziale restituisce un option
, ciò comporterà un'allocazione per il valore Some
in caso di un confronto riuscito. Per evitarlo, è possibile usare l'opzione di valore come valore restituito attraverso l'attributo Struct
.
open System
[<return: Struct>]
let (|Int|_|) str =
match Int32.TryParse(str) with
| (true, n) -> ValueSome n
| _ -> ValueNone
L'attributo deve essere specificato, poiché l'uso di una restituzione di una struttura non è dedotto semplicemente modificando il tipo restituito in ValueOption
. Per altre informazioni, vedere RFC FS-1039.