Criteri attivi

I modelli attivi consentono di definire partizioni denominate che suddividono i dati di input, in modo da poter usare questi nomi in un'espressione di ricerca di criteri esattamente come per un'unione discriminata. È possibile usare i criteri attivi per decomporre 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.
// Uses a FSharp.Core.option<_> 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 form 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 |) sono denominati clip banana e la funzione creata da questo tipo di associazione let è detta riconoscitore attivo.

Si consideri ad esempio il modello attivo seguente con un argomento .

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

È possibile usare il criterio attivo in un'espressione di ricerca di criteri, 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 Color oggetto 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 ricerca dei criteri risultanti consentono la scrittura dei dati in modo pratico, molto leggibile, semplificando notevolmente la diramazione e il codice di analisi dei dati potenzialmente complessi.

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 producono sempre un valore sono denominati 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 che si escludono a vicenda, ma non devono essere. Nell'esempio seguente il criterio Square e il modello Cube non sono contigui, 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 viene applicato il modello attivo con parametri per il nome. 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 Integer attivo parziale 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 corrispondenza dei criteri, ma anche alle associazioni let.Active patterns are not restricted to pattern matching expressions, you can also use them on let-bindings.

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 maiuscole e minuscole.

// 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

Rappresentazioni di struct per modelli attivi parziali

Per impostazione predefinita, i modelli attivi parziali restituiscono un option valore, che comporta un'allocazione per il Some valore in caso di corrispondenza riuscita. In alternativa, è possibile usare un'opzione value come valore restituito tramite l'uso dell'attributo Struct :

open System

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

L'attributo deve essere specificato, perché l'uso di una restituzione dello struct non viene dedotto dalla semplice modifica del tipo restituito in ValueOption. Per altre informazioni, vedere RFC FS-1039.

Vedi anche