Modelli attivi (F#)
I modelli attivi consentono di definire partizioni denominate che suddividono i dati di input, in modo che sia possibile utilizzare tali nomi in un'espressione di corrispondenza dei modelli analogamente a quanto avviene con un'unione discriminata.È possibile utilizzare i modelli attivi per scomporre i dati in modo personalizzato per ogni partizione.
// Complete active pattern definition.
let (|identifer1|identifier2|...|) [ arguments ] = expression
// Partial active pattern definition.
let (|identifier|_|) [ arguments ] = expression
Note
Nella sintassi precedente gli identificatori sono nomi per le partizioni dei dati di input, rappresentati da arguments o, in altre parole, nomi per sottoinsiemi del set di tutti i valori degli argomenti.Possono essere presenti fino a sette partizioni in una definizione attiva del modello.expression descrive la forma in cui scomporre i dati.È possibile utilizzare una definizione di modello attivo per definire le regole per determinare a quali partizioni denominate appartengono i valori forniti come argomenti.I simboli (| e |) vengono definiti banana clip e la funzione creata da questo tipo di associazione let è detta riconoscimento attivo.
Considerare ad esempio il criterio attivo seguente con un argomento.
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
È possibile utilizzare il criterio attivo in un'espressione di corrispondenza dei modelli, 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 del programma è il seguente:
7 is odd
11 is odd
32 is even
I modelli attivi possono anche essere utilizzati per scomporre i tipi di dati in più modi, ad esempio quando gli stessi dati sottostanti dispongono di varie rappresentazioni possibili.Un oggetto Color può ad esempio 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
R: 255 G: 0 B: 0
H: 0.000000 S: 1.000000 B: 0.500000
Black
R: 0 G: 0 B: 0
H: 0.000000 S: 0.000000 B: 0.000000
White
R: 255 G: 255 B: 255
H: 0.000000 S: 0.000000 B: 1.000000
Gray
R: 128 G: 128 B: 128
H: 0.000000 S: 0.000000 B: 0.501961
BlanchedAlmond
R: 255 G: 235 B: 205
H: 36.000000 S: 1.000000 B: 0.901961
Queste due modalità di utilizzo dei modelli attivi consentono, in combinazione, di partizionare e scomporre i dati nella forma appropriata e di eseguire i calcoli appropriati sui dati appropriati, nella forma più conveniente per il calcolo.
Le espressioni di corrispondenza dei modelli risultanti consentono di scrivere i dati in modo ottimale, rendendoli molto più facili da leggere e semplificando notevolmente la potenziale complessità della creazione di rami e del codice di analisi dei dati.
Modelli attivi parziali
È a volte necessario partizionare solo una parte dello spazio di input.In tal caso, è possibile scrivere un set di modelli parziali, ognuno dei quali corrisponde a una parte di input e non agli altri input.I criteri attivi che non sempre producono un valore sono detti criteri attivi parziali e il relativo tipo restituito è un tipo di opzione.Per definire un modello attivo parziale, si utilizza un carattere jolly (_) alla fine dell'elenco di modelli interni all'operatore banana clip.Nel codice seguente viene illustrato l'utilizzo 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"
Di seguito è riportato l'output dell'esempio precedente:
1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.
Quando si utilizzano modelli attivi parziali, talvolta le singole scelte possono essere disgiunte o escludersi a vicenda, ma questo non è necessario.Nell'esempio seguente il criterio Square e il criterio Cube non sono disgiunti, in quanto alcuni numeri sono sia quadrati che cubi, ad esempio 64.Il programma seguente consente di visualizzare tutti gli interi fino a 1.000.000 che rappresentano sia quadrati che 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 examineNumber x =
match x with
| Cube x -> printfn "%d is a cube" x
| _ -> ()
match x with
| Square x -> printfn "%d is a square" x
| _ -> ()
let findSquareCubes x =
if (match x with
| Cube x -> true
| _ -> false
&&
match x with
| Square x -> true
| _ -> false
)
then printf "%d \n" x
[ 1 .. 1000000 ] |> List.iter (fun elem -> findSquareCubes elem)
L'output è indicato di seguito:
1
64
729
4096
15625
46656
117649
262144
531441
1000000
Modelli attivi con parametri
I modelli attivi accettano sempre almeno un argomento per l'elemento di cui viene cercata la corrispondenza, ma possono accettare anche argomenti aggiuntivi e in tal caso si parla di modello attivo con parametri.Gli argomenti aggiuntivi consentono di rendere specifico un modello generale.I modelli attivi che utilizzano espressioni regolari per analizzare le stringhe, ad esempio, includono spesso l'espressione regolare come parametro aggiuntivo, come nel codice seguente, in cui viene utilizzato anche il modello attivo parziale Integer definito nell'esempio di codice precedente.In questo esempio, le stringhe che utilizzano espressioni regolari per vari formati di data vengono fornite per personalizzare il modello attivo generale ParseRegex.Il modello attivo Integer viene utilizzato 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())
Di seguito è riportato l'output del codice precedente:
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