Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
I modelli sono regole per la trasformazione dei dati di input. Vengono usati in F# per confrontare i dati con una struttura logica o strutture, scomporre i dati in parti costitutive o estrarre informazioni dai dati in diversi modi.
Osservazioni
I modelli vengono usati in molti costrutti di linguaggio, ad esempio l'espressione match. Vengono usati quando si elaborano argomenti per le funzioni nelle associazioni let, nelle espressioni lambda e nei gestori di eccezioni associati all'espressione try...with. Per altre informazioni, vedere espressioni di corrispondenza, let binding, espressioni lambda: parola chiave fune eccezioni : l'espressione try...with.
Nell'espressione match, ad esempio, il criterio segue il simbolo della pipe.
match expression with
| pattern [ when condition ] -> result-expression
...
Ogni modello funge da regola per trasformare l'input in qualche modo. Nell'espressione match, ogni modello viene esaminato a turno per verificare se i dati di input sono compatibili con il modello. Se viene trovata una corrispondenza, l'espressione risultante viene eseguita. Se non viene trovata una corrispondenza, viene testata la regola del modello successiva. La parte facoltativa quando condizione è illustrata in Espressioni di confronto.
I modelli supportati sono illustrati nella tabella seguente. In fase di esecuzione, l'input viene testato in base a ognuno dei modelli seguenti nell'ordine elencato nella tabella e i modelli vengono applicati in modo ricorsivo, dal primo all'ultimo come appaiono nel codice e da sinistra a destra per i modelli in ogni riga.
| Nome | Descrizione | Esempio |
|---|---|---|
| Modello costante | Qualsiasi valore letterale numerico, carattere o stringa, costante di enumerazione o identificatore letterale definito |
1.0, "test", 30Color.Red |
| Modello di identificatore | Valore case di un'unione discriminata, un'etichetta di eccezione o un caso di modello attivo | Some(x)Failure(msg) |
| Modello di variabile | identificatore | a |
modello as |
modello come identificatore | (a, b) as tuple1 |
| Modello OR | pattern1 | pattern2 | ([h] | [h; _]) |
| Modello AND | pattern1 & pattern2 | (a, b) & (_, "test") |
| Modello degli svantaggi | identificatore :: identificatore di elenco | h :: t |
| Schema elenco | [ pattern_1; ... ; pattern_n ] | [ a; b; c ] |
| Schema di un array | [| pattern_1; ..; pattern_n |] | [| a; b; c |] |
| Modello racchiuso tra parentesi | ( modello ) | ( a ) |
| Modello di tupla | ( pattern_1, ... , pattern_n ) | ( a, b ) |
| Schema di registrazione | { identifier1 = pattern_1; ... ; = identifier_npattern_n } | { Name = name; } |
| Modello con caratteri jolly | _ | _ |
| Pattern insieme all'annotazione del tipo | modello : tipo | a : int |
| Modello di test tipo | :? tipo [ come identificatore ] | :? System.DateTime as dt |
| Modello Null | nullo | null |
| Nome del modello Nameof | nameof expr | nameof str |
Modelli costanti
I modelli costanti sono valori letterali numerici, di carattere e di stringa, costanti di enumerazione (incluso il nome del tipo di enumerazione). Un'espressione match che include solo modelli costanti può essere confrontata con un'istruzione case in altri linguaggi. L'input viene confrontato con il valore letterale e il modello corrisponde se i valori sono uguali. Il tipo di valore letterale deve essere compatibile con il tipo di input.
L'esempio seguente illustra l'uso di modelli letterali e usa anche un modello di variabile e un modello OR.
[<Literal>]
let Three = 3
let filter123 x =
match x with
// The following line contains literal patterns combined with an OR pattern.
| 1 | 2 | Three -> printfn "Found 1, 2, or 3!"
// The following line contains a variable pattern.
| var1 -> printfn "%d" var1
for x in 1..10 do filter123 x
Un altro esempio di modello letterale è un modello basato sulle costanti di enumerazione. È necessario specificare il nome del tipo di enumerazione quando si utilizzano costanti di enumerazione.
type Color =
| Red = 0
| Green = 1
| Blue = 2
let printColorName (color:Color) =
match color with
| Color.Red -> printfn "Red"
| Color.Green -> printfn "Green"
| Color.Blue -> printfn "Blue"
| _ -> ()
printColorName Color.Red
printColorName Color.Green
printColorName Color.Blue
Modelli di identificatore
Se il criterio è una stringa di caratteri che forma un identificatore valido, il formato dell'identificatore determina come il criterio viene confrontato. Se l'identificatore è più lungo di un singolo carattere e inizia con un carattere maiuscolo, il compilatore tenta di trovare una corrispondenza con il modello di identificatore. L'identificatore di questo modello può essere un valore contrassegnato con l'attributo Literal, un caso di unione discriminante, un identificatore di eccezione o un caso di criterio attivo. Se non viene trovato alcun identificatore corrispondente, la corrispondenza ha esito negativo e la regola del modello successiva, il modello di variabile, viene confrontato con l'input.
I modelli di unione discriminati possono essere casi semplici nominati oppure avere un valore o una tupla contenente più valori. Se è presente un valore, è necessario specificare un identificatore per il valore. Nel caso di una tupla, è necessario fornire un modello di tupla con un identificatore per ogni elemento della tupla o un identificatore con un nome di campo per uno o più campi unione denominati. Per esempi, vedere gli esempi di codice in questa sezione.
Il tipo di option è un'unione discriminata con due casi, Some e None. Un caso (Some) ha un valore, ma l'altro (None) è solo un caso nominato. Pertanto, Some deve avere una variabile per il valore associato al caso Some, ma None deve essere presente da solo. Nel codice seguente, alla variabile var1 viene assegnato il valore ottenuto corrispondendo al caso Some.
let printOption (data : int option) =
match data with
| Some var1 -> printfn "%d" var1
| None -> ()
Nell'esempio seguente la PersonName unione discriminata contiene una combinazione di stringhe e caratteri che rappresentano possibili forme di nomi. I casi dell'unione discriminata sono FirstOnly, LastOnlye FirstLast.
type PersonName =
| FirstOnly of string
| LastOnly of string
| FirstLast of string * string
let constructQuery personName =
match personName with
| FirstOnly(firstName) -> printf "May I call you %s?" firstName
| LastOnly(lastName) -> printf "Are you Mr. or Ms. %s?" lastName
| FirstLast(firstName, lastName) -> printf "Are you %s %s?" firstName lastName
Per le unioni discriminate con campi denominati, usare il segno di uguale (=) per estrarre il valore di un campo denominato. Si consideri, ad esempio, un'unione discriminata con una dichiarazione simile alla seguente.
type Shape =
| Rectangle of height : float * width : float
| Circle of radius : float
È possibile usare i campi denominati in un'espressione di confronto come indicato di seguito.
let matchShape shape =
match shape with
| Rectangle(height = h) -> printfn $"Rectangle with length %f{h}"
| Circle(r) -> printfn $"Circle with radius %f{r}"
L'uso del campo denominato è facoltativo, quindi nell'esempio precedente, sia Circle(r) che Circle(radius = r) hanno lo stesso effetto.
Quando si specificano più campi, usare il punto e virgola (;) come separatore.
match shape with
| Rectangle(height = h; width = w) -> printfn $"Rectangle with height %f{h} and width %f{w}"
| _ -> ()
I modelli attivi consentono di definire schemi di corrispondenza personalizzati più complessi. Per altre informazioni sui modelli attivi, vedere Modelli attivi.
Il caso in cui l'identificatore è un'eccezione viene utilizzato nella corrispondenza di modelli nel contesto dei gestori di eccezioni. Per informazioni sulla corrispondenza dei pattern nella gestione delle eccezioni, vedere Exceptions: The try...with Expression.
Schemi Variabili
Lo schema di variabile assegna il valore corrispondente a un nome di variabile, che può quindi essere utilizzato nell'espressione operativa a destra del simbolo ->. Un modello di variabile corrisponde da solo a qualsiasi input, ma i modelli di variabile vengono spesso visualizzati all'interno di altri modelli, consentendo quindi strutture più complesse, ad esempio tuple e matrici, di essere scomposte in variabili.
Nell'esempio seguente viene illustrato un modello di variabile all'interno di un modello di tupla.
let function1 x =
match x with
| (var1, var2) when var1 > var2 -> printfn "%d is greater than %d" var1 var2
| (var1, var2) when var1 < var2 -> printfn "%d is less than %d" var1 var2
| (var1, var2) -> printfn "%d equals %d" var1 var2
function1 (1,2)
function1 (2, 1)
function1 (0, 0)
come modello
Il modello as è un modello con una clausola as aggiunta. La clausola as associa il valore corrispondente a un nome che può essere utilizzato nell'espressione di esecuzione di un'espressione match oppure, nel caso in cui questo modello venga usato in un'associazione let, il nome viene aggiunto come associazione all'ambito locale.
Nell'esempio seguente viene usato il modello as.
let (var1, var2) as tuple1 = (1, 2)
printfn "%d %d %A" var1 var2 tuple1
Modello OR
Il modello OR viene usato quando i dati di input possono corrispondere a più modelli e si vuole eseguire lo stesso codice di conseguenza. I tipi di entrambi i lati del modello OR devono essere compatibili.
Nell'esempio seguente viene illustrato il modello OR.
let detectZeroOR point =
match point with
| (0, 0) | (0, _) | (_, 0) -> printfn "Zero found."
| _ -> printfn "Both nonzero."
detectZeroOR (0, 0)
detectZeroOR (1, 0)
detectZeroOR (0, 10)
detectZeroOR (10, 15)
Modello AND
Il modello AND richiede che l'input corrisponda a due modelli. I tipi di entrambi i lati del modello AND devono essere compatibili.
L'esempio seguente è simile detectZeroTuple illustrato nella sezione Modello di tupla più avanti in questo argomento, ma in questo caso var1 e var2 vengono ottenuti come valori usando il modello AND.
let detectZeroAND point =
match point with
| (0, 0) -> printfn "Both values zero."
| (var1, var2) & (0, _) -> printfn "First value is 0 in (%d, %d)" var1 var2
| (var1, var2) & (_, 0) -> printfn "Second value is 0 in (%d, %d)" var1 var2
| _ -> printfn "Both nonzero."
detectZeroAND (0, 0)
detectZeroAND (1, 0)
detectZeroAND (0, 10)
detectZeroAND (10, 15)
Modello cons
Il pattern cons viene usato per scomporre un elenco nel primo elemento, il head, e un elenco che contiene gli elementi rimanenti, il tail.
let list1 = [ 1; 2; 3; 4 ]
// This example uses a cons pattern and a list pattern.
let rec printList l =
match l with
| head :: tail -> printf "%d " head; printList tail
| [] -> printfn ""
printList list1
È anche possibile concatenare più modelli di cons per trovare le corrispondenze con elenchi che iniziano con sequenze specifiche di elementi.
let charList = ['A'; 'B'; 'C'; 'D']
// This example demonstrates multiple cons patterns.
let matchChars xs =
match xs with
| 'A'::'B'::t -> printfn "starts with 'AB', rest: %A" t
| 'A'::t -> printfn "starts with 'A', rest: %A" t
| 'C'::'D'::t -> printfn "starts with 'CD', rest: %A" t
| _ -> printfn "does not match"
matchChars charList
matchChars ['A'; 'X']
matchChars ['C'; 'D'; 'E']
Modello elenco
Il modello di elenco consente di scomporre gli elenchi in un numero di elementi. Il modello di elenco stesso può corrispondere solo agli elenchi di un numero specifico di elementi.
// This example uses a list pattern.
let listLength list =
match list with
| [] -> 0
| [ _ ] -> 1
| [ _; _ ] -> 2
| [ _; _; _ ] -> 3
| _ -> List.length list
printfn "%d" (listLength [ 1 ])
printfn "%d" (listLength [ 1; 1 ])
printfn "%d" (listLength [ 1; 1; 1; ])
printfn "%d" (listLength [ ] )
Modello di matrice
Il modello di matrice è simile al modello di elenco e può essere usato per scomporre le matrici di una lunghezza specifica.
// This example uses array patterns.
let vectorLength vec =
match vec with
| [| var1 |] -> var1
| [| var1; var2 |] -> sqrt (var1*var1 + var2*var2)
| [| var1; var2; var3 |] -> sqrt (var1*var1 + var2*var2 + var3*var3)
| _ -> failwith (sprintf "vectorLength called with an unsupported array size of %d." (vec.Length))
printfn "%f" (vectorLength [| 1. |])
printfn "%f" (vectorLength [| 1.; 1. |])
printfn "%f" (vectorLength [| 1.; 1.; 1.; |])
printfn "%f" (vectorLength [| |] )
Modello racchiuso tra parentesi
Le parentesi possono essere raggruppate intorno ai modelli per ottenere l'associatività desiderata. Nell'esempio seguente, le parentesi vengono usate per controllare l'associatività tra un modello AND e un modello cons.
let countValues list value =
let rec checkList list acc =
match list with
| (elem1 & head) :: tail when elem1 = value -> checkList tail (acc + 1)
| head :: tail -> checkList tail acc
| [] -> acc
checkList list 0
let result = countValues [ for x in -10..10 -> x*x - 4 ] 0
printfn "%d" result
Modello di tupla
Il modello di tupla corrisponde agli input in forma di tupla e consente di scomporre la tupla nei suoi elementi costitutivi utilizzando variabili di pattern matching per ogni posizione nella tupla.
Nell'esempio seguente viene illustrato il modello di tupla e vengono usati anche modelli letterali, modelli di variabili e il modello con caratteri jolly.
let detectZeroTuple point =
match point with
| (0, 0) -> printfn "Both values zero."
| (0, var2) -> printfn "First value is 0 in (0, %d)" var2
| (var1, 0) -> printfn "Second value is 0 in (%d, 0)" var1
| _ -> printfn "Both nonzero."
detectZeroTuple (0, 0)
detectZeroTuple (1, 0)
detectZeroTuple (0, 10)
detectZeroTuple (10, 15)
Modello di record
Il modello di record viene usato per scomporre i record per estrarre i valori dei campi. Il modello non deve fare riferimento a tutti i campi del record; i campi omessi semplicemente non partecipano alla corrispondenza e non vengono estratti.
// This example uses a record pattern.
type MyRecord = { Name: string; ID: int }
let IsMatchByName record1 (name: string) =
match record1 with
| { MyRecord.Name = nameFound; MyRecord.ID = _; } when nameFound = name -> true
| _ -> false
let recordX = { Name = "Parker"; ID = 10 }
let isMatched1 = IsMatchByName recordX "Parker"
let isMatched2 = IsMatchByName recordX "Hartono"
Modello con caratteri jolly
Il modello con caratteri jolly è rappresentato dal carattere trattino basso (_) e corrisponde a qualsiasi input, proprio come il modello di variabile, ad eccezione del fatto che l'input viene eliminato anziché assegnato a una variabile. Il pattern con caratteri jolly viene spesso usato all'interno di altri schemi come segnaposto per i valori che non sono richiesti nell'espressione a destra del simbolo ->. Il criterio con caratteri jolly viene spesso usato anche alla fine di un elenco di modelli per trovare una corrispondenza con qualsiasi input non corrispondente. Il modello con caratteri jolly è illustrato in molti esempi di codice in questo argomento. Per un esempio, vedere il codice precedente.
Il codice seguente illustra alcuni usi aggiuntivi del modello con caratteri jolly:
// Wildcard pattern matching "nothing" examples
// Example 1: Wildcard ignoring function parameters
let ignoreAllParams _ _ = "Ignores all input"
// Example 2: Wildcard in destructuring, ignoring elements
let getFirstOnly (first, _) = first
// Example 3: Using wildcard to ignore optional values
let handleEmpty opt =
match opt with
| Some _ -> "Has something"
| None -> "Has nothing"
// Usage
printfn "%s" (ignoreAllParams 42 "test")
printfn "%d" (getFirstOnly (1, "ignored"))
printfn "%s" (handleEmpty None)
Modelli con annotazioni di tipo
I modelli possono avere annotazioni di tipo. Questi si comportano come altre annotazioni di tipo e guidano l'inferenza allo stesso modo. Le parentesi sono necessarie intorno alle annotazioni dei tipi nei modelli.
Un criterio con un'annotazione di tipo usa la sintassi pattern : type e fornisce informazioni sul tipo in fase di compilazione per il controllo del tipo. Si tratta semplicemente di un'annotazione di tipo statico che consente l'inferenza del tipo. Non esegue alcun controllo o conversione del tipo di runtime. Il compilatore usa queste informazioni durante la compilazione per determinare il tipo della variabile di pattern.
Il codice seguente illustra un modello con un'annotazione di tipo:
let detect1 x =
match x with
| 1 -> printfn "Found a 1!"
| (var1 : int) -> printfn "%d" var1
detect1 0
detect1 1
In questo esempio indica (var1 : int) al compilatore var1 che è di tipo int. Questo viene risolto in fase di compilazione e il codice generato considera var1 come un numero intero nell'espressione di corrispondenza. Questo modello corrisponderà a qualsiasi valore intero e lo associa a var1.
Caratteristiche chiave:
- Usa la sintassi
pattern : type(con un singolo punto). - Risolto in fase di compilazione : fornisce informazioni sul tipo al controllo del tipo.
- Non esegue il test del tipo di runtime.
- Usato per l'inferenza del tipo e per guidare il compilatore.
Test di modello tipo
Il modello di test del tipo viene usato per associare l'input a un tipo in fase di esecuzione. Se il tipo di input corrisponde a (o è un tipo derivato di) il tipo specificato nel modello, la corrispondenza ha esito positivo.
Un modello di test di tipo usa la sintassi :? type ed esegue il controllo dei is tipi di runtime, simile agli operatori o as in C#. Questo modello verifica se un valore è di un tipo specifico durante l'esecuzione del programma, rendendolo utile quando si utilizzano gerarchie di ereditarietà o implementazioni dell'interfaccia.
L'esempio seguente illustra il modello di test del tipo:
open System.Windows.Forms
let RegisterControl(control:Control) =
match control with
| :? Button as button -> button.Text <- "Registered."
| :? CheckBox as checkbox -> checkbox.Text <- "Registered."
| _ -> ()
Se si controlla solo se un identificatore è di un tipo derivato specifico, non è necessaria la parte as identifier del modello, come illustrato nell'esempio seguente:
type A() = class end
type B() = inherit A()
type C() = inherit A()
let m (a: A) =
match a with
| :? B -> printfn "It's a B"
| :? C -> printfn "It's a C"
| _ -> ()
Caratteristiche chiave:
- Usa la sintassi
:? typeo:? type as identifier(con un punto interrogativo). - Risolto in fase di esecuzione : esegue il controllo effettivo dei tipi durante l'esecuzione.
- Verifica se un valore è un'istanza di un tipo specifico o dei relativi tipi derivati.
- Comunemente usato con gerarchie di ereditarietà e tipi polimorfici.
- Simile all'operatore o
asall'operatoreisC#.
Annotazioni dei tipi a contrasto e modelli di test dei tipi
Anche se entrambi i modelli implicano tipi, servono scopi molto diversi:
| Caratteristica / Funzionalità | Modello di annotazione dei tipi (pattern : type) |
Modello di test di tipo (:? type) |
|---|---|---|
| Syntax | Punti singoli: a : int |
Due punti con punto interrogativo: :? Button |
| Quando viene risolto | Tempo di compilazione | Runtime |
| Purpose | Inferenza dei tipi guide | Verifica il tipo effettivo di valore |
| caso d'uso | Aiutare il compilatore a comprendere i tipi | Controllo dei tipi di runtime nelle gerarchie di ereditarietà |
| Equivalente in C# | Annotazioni dei tipi nei modelli switch |
isoperatori o as |
L'esempio seguente illustra le differenze:
// Type annotation pattern - compile time
let detect1 x =
match x with
| 1 -> printfn "Found a 1!"
| (var1 : int) -> printfn "%d" var1
// The ': int' tells the compiler var1 is an int
// Everything is resolved at compile time
// Type test pattern - runtime
type A() = class end
type B() = inherit A()
let test (a: A) =
match a with
| :? B -> printfn "Runtime check: it's a B"
| _ -> printfn "Runtime check: it's not a B"
// The ':? B' performs a runtime type check
// The actual type is tested during execution
Modello Null
Il modello null corrisponde al valore null che può presentarsi quando si utilizzano tipi che consentono un valore null. I modelli Null vengono spesso usati durante l'interoperabilità con il codice .NET Framework. Ad esempio, il valore restituito di un'API .NET potrebbe essere l'input di un'espressione match. È possibile controllare il flusso del programma in base all'eventuale valore restituito null e anche ad altre caratteristiche del valore restituito. È possibile usare il modello Null per impedire la propagazione dei valori Null al resto del programma.
Nell'esempio seguente vengono usati il criterio Null e il modello di variabile.
let ReadFromFile (reader : System.IO.StreamReader) =
match reader.ReadLine() with
| null -> printfn "\n"; false
| line -> printfn "%s" line; true
let fs = System.IO.File.Open("..\..\Program.fs", System.IO.FileMode.Open)
let sr = new System.IO.StreamReader(fs)
while ReadFromFile(sr) = true do ()
sr.Close()
Il pattern Null è consigliato anche per le capacità di gestire la nullabilità di F# 9 :
let len (str: string | null) =
match str with
| null -> -1
| s -> s.Length
Analogamente, è possibile usare nuovi modelli di correlati ai valori Nullbility dedicati:
let len str = // str is inferred to be `string | null`
match str with
| Null -> -1
| NonNull (s: string) -> s.Length
Nome del modello Nameof
Il modello nameof corrisponde a una stringa quando il suo valore è uguale all'espressione che segue la parola chiave nameof. Questo modello è particolarmente utile quando è necessario associare i valori stringa ai nomi di tipi, casi di unione discriminanti o altri simboli nel codice. L'uso nameof di fornisce sicurezza in fase di compilazione perché se si rinomina un simbolo, il modello userà automaticamente il nuovo nome.
Un caso d'uso comune consiste nel deserializzare i dati in cui i valori stringa rappresentano nomi di tipo o di maiuscole e minuscole:
type EventType =
| OrderCreated
| OrderShipped
| OrderDelivered
let handleEvent eventName data =
match eventName with
| nameof OrderCreated -> printfn "Processing order creation: %s" data
| nameof OrderShipped -> printfn "Processing order shipment: %s" data
| nameof OrderDelivered -> printfn "Processing order delivery: %s" data
| _ -> printfn "Unknown event type: %s" eventName
handleEvent "OrderCreated" "Order #123" // matches first case
handleEvent "OrderShipped" "Order #123" // matches second case
Questo approccio è preferibile rispetto all'uso di valori letterali stringa (ad esempio "OrderCreated") perché:
- Se si rinomina
OrderCreatedinOrderPlaced, il modello viene aggiornato automaticamente. - Il compilatore garantisce che il simbolo esista, impedendo errori di digitazioni.
- Il codice rimane coerente durante il refactoring.
È anche possibile usare nameof con i parametri:
let f (str: string) =
match str with
| nameof str -> "It's 'str'!"
| _ -> "It is not 'str'!"
f "str" // matches
f "asdf" // does not match
Vedere l'operatore nameof per informazioni su che cosa puoi denominare.