Parametri e argomenti (F#)
In questo argomento viene descritto il supporto del linguaggio per la definizione di parametri e il passaggio di argomenti a funzioni, metodi e proprietà. Include informazioni sui parametri pass-by-reference e come definire e utilizzare metodi che possono utilizzare un numero variabile di argomenti.
Parametri e argomenti
Il termine parametro è utilizzato per descrivere i nomi per i valori che si prevede vengano forniti. Il termine argomento è utilizzato per i valori forniti per ogni parametro.
I parametri possono essere specificati in formato di tupla o sottoposto a currying o con una combinazione dei due. È possibile passare gli argomenti utilizzando un nome di parametro esplicito. I parametri dei metodi possono essere specificati come facoltativi ed è possibile assegnare loro un valore predefinito.
Modelli di parametri
I parametri forniti alle funzioni e ai metodi sono, in genere, modelli separati da spazi. Questo significa che, teoricamente, tutti i modelli descritti in Espressioni match (F#) possono essere utilizzati in un elenco di parametri per una funzione o un membro.
I metodi prevedono in genere l'utilizzo del formato tupla per il passaggio di argomenti. In questo modo è possibile ottenere un risultato più chiaro dal punto di vista degli altri linguaggi .NET, in quanto il formato tupla corrisponde al modo in cui vengono passati gli argomenti nei metodi .NET.
Il formato sottoposto a currying viene utilizzato più spesso con le funzioni create utilizzando associazioni let.
Nello pseudocodice seguente sono illustrati alcuni esempi di argomento in formato tupla e sottoposto a currying.
// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...
È possibile utilizzare formati combinati quando alcuni argomenti sono di tipo tupla e altri no.
let function2 param1 (param2a, param2b) param3 = ...
Negli elenchi di parametri è possibile utilizzare altri modelli, ma se il modello di parametri non corrisponde a tutti gli input possibili, potrebbe verificarsi una corrispondenza non completa in fase di esecuzione. L'eccezione MatchFailureException viene generata quando il valore di un argomento non corrisponde ai modelli specificati nell'elenco di parametri. Il compilatore genera un avviso quando un modello di parametri consente corrispondenze incomplete. Per gli elenchi di parametri viene generalmente utilizzato almeno un altro modello, ovvero il modello carattere jolly. Tale modello viene utilizzato in un elenco di parametri quando si desidera semplicemente ignorare qualsiasi argomento fornito. Nel codice seguente viene illustrato l'utilizzo del modello carattere jolly in un elenco di argomenti.
let makeList _ = [ for i in 1 .. 100 -> i * i ]
// The arguments 100 and 200 are ignored.
let list1 = makeList 100
let list2 = makeList 200
Il modello carattere jolly può essere utile quando non sono necessari gli argomenti passati, ad esempio nel punto di ingresso principale in un programma, quando non vi è interesse per gli argomenti della riga di comando che vengono generalmente forniti come matrice di stringhe, come nel codice seguente.
[<EntryPoint>]
let main _ =
printfn "Entry point!"
0
Altri modelli talvolta utilizzati negli argomenti sono il modello as e i modelli identificatori associati a unioni discriminate e modelli attivi. È possibile utilizzare il modello di unione discriminata a case singolo come indicato di seguito.
type Slice = Slice of int * int * string
let GetSubstring1 (Slice(p0, p1, text)) =
printfn "Data begins at %d and ends at %d in string %s" p0 p1 text
text.[p0..p1]
let substring = GetSubstring1 (Slice(0, 4, "Et tu, Brute?"))
printfn "Substring: %s" substring
L'output è indicato di seguito.
Data begins at 0 and ends at 4 in string Et tu, Brute?
Et tu
I modelli attivi possono essere utili come parametri, ad esempio quando trasformano un argomento in un formato desiderato, come nel seguente esempio:
type Point = { x : float; y : float }
let (| Polar |) { x = x; y = y} =
( sqrt (x*x + y*y), System.Math.Atan (y/ x) )
let radius (Polar(r, _)) = r
let angle (Polar(_, theta)) = theta
È possibile utilizzare il modello as per archiviare un valore corrispondente come valore locale, come illustrato nella riga di codice seguente.
let GetSubstring2 (Slice(p0, p1, text) as s) = s
Un altro modello utilizzato occasionalmente è una funzione che lascia l'ultimo argomento senza nome fornendo, come corpo della funzione, un'espressione lambda che esegue immediatamente una corrispondenza di modelli nell'argomento implicito. Un esempio di questo modello è fornito nella riga di codice seguente.
let isNil = function [] -> true | _::_ -> false
Questo codice definisce una funzione che accetta un elenco generico e restituisce true se l'elenco è vuoto e false se non lo è. L'utilizzo di tali tecniche può rendere più complessa la lettura del codice.
Occasionalmente, i modelli che comportano corrispondenze incomplete possono essere utili. Se, ad esempio, si è a conoscenza del fatto che gli elenchi nel programma sono costituiti solo da tre elementi, è possibile utilizzare un modello analogo al seguente in un elenco di parametri.
let sum [a; b; c;] = a + b + c
L'utilizzo di modelli con corrispondenze incomplete è consigliabile per la creazione rapida di prototipi e altri utilizzi temporanei. Tale codice comporta la generazione di un avviso da parte del compilatore. Tali modelli non sono adeguati per il caso generale di tutti gli input possibili, pertanto non sono appropriati per le API dei componenti.
Argomenti denominati
Gli argomenti per i metodi possono essere specificati in base alla posizione in un elenco di argomenti delimitati da virgole oppure possono essere passati in modo esplicito a un metodo fornendo il nome, seguito da un segno di uguale e dal valore che deve essere passato. Se gli argomenti vengono specificati fornendo il nome, possono trovarsi in un ordine diverso rispetto a quello utilizzato nella dichiarazione.
Gli argomenti denominati semplificano la lettura del codice e lo rendono più adattabile a determinati tipi di modifiche nell'API, ad esempio un riordinamento di parametri del metodo.
Gli argomenti denominati sono consentiti solo per i metodi e non per le funzioni con associazione let, i valori di funzioni o le espressioni lambda.
Nell'esempio di codice seguente viene illustrato l'utilizzo di argomenti denominati.
type SpeedingTicket() =
member this.GetMPHOver(speed: int, limit: int) = speed - limit
let CalculateFine (ticket : SpeedingTicket) =
let delta = ticket.GetMPHOver(limit = 55, speed = 70)
if delta < 20 then 50.0 else 100.0
let ticket1 : SpeedingTicket = SpeedingTicket()
printfn "%f" (CalculateFine ticket1)
In una chiamata al costruttore della classe, è possibile impostare i valori delle proprietà della classe utilizzando una sintassi simile a quella degli argomenti predefiniti. Questa sintassi è illustrata nell'esempio seguente.
type Account() =
let mutable balance = 0.0
let mutable number = 0
let mutable firstName = ""
let mutable lastName = ""
member this.AccountNumber
with get() = number
and set(value) = number <- value
member this.FirstName
with get() = firstName
and set(value) = firstName <- value
member this.LastName
with get() = lastName
and set(value) = lastName <- value
member this.Balance
with get() = balance
and set(value) = balance <- value
member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount
let account1 = new Account(AccountNumber=8782108,
FirstName="Darren", LastName="Parker",
Balance=1543.33)
Per ulteriori informazioni, vedere Costruttori (F#).
Parametri facoltativi
È possibile specificare un parametro facoltativo per un metodo utilizzando un punto interrogativo davanti al nome del parametro. I parametri facoltativi vengono interpretati come il tipo di opzione F#, pertanto è possibile eseguire una query su di essi nello stesso modo utilizzato per i tipi di opzione, utilizzando l'espressione match con Some e None. I parametri facoltativi sono consentiti solo nei membri, non nelle funzioni create utilizzando associazioni let.
È anche possibile utilizzare una funzione defaultArg, che consente di impostare un valore predefinito di un argomento facoltativo. La funzione defaultArg accetta il parametro facoltativo come primo argomento e il valore predefinito come secondo.
Nell'esempio seguente viene illustrato l'utilizzo dei parametri facoltativi.
type DuplexType =
| Full
| Half
type Connection(?rate0 : int, ?duplex0 : DuplexType, ?parity0 : bool) =
let duplex = defaultArg duplex0 Full
let parity = defaultArg parity0 false
let mutable rate = match rate0 with
| Some rate1 -> rate1
| None -> match duplex with
| Full -> 9600
| Half -> 4800
do printfn "Baud Rate: %d Duplex: %A Parity: %b" rate duplex parity
let conn1 = Connection(duplex0 = Full)
let conn2 = Connection(duplex0 = Half)
let conn3 = Connection(300, Half, true)
L'output è indicato di seguito.
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false
Baud Rate: 300 Duplex: Half Parity: true
Passaggio per riferimento
F# supporta la parola chiave byref, che specifica che un parametro viene passato per riferimento. Questo significa che eventuali modifiche del valore vengono mantenute dopo l'esecuzione della funzione. I valori forniti al parametro byref devono essere modificabili. In alternativa, è possibile passare celle di riferimento del tipo appropriato.
Il passaggio per riferimento nei linguaggi .NET si è evoluto come metodo per restituire più di un valore da una funzione. In F# è possibile restituire una tupla a questo scopo oppure utilizzare una cella di riferimento modificabile come parametro. Il parametro byref viene fornito principalmente per offrire interoperabilità con le librerie .NET.
Negli esempi seguenti viene illustrato l'utilizzo della parola chiave byref. Si noti che, quando si utilizza una cella di riferimento come parametro, è necessario creare una cella di riferimento come valore denominato e utilizzarla come parametro e non è sufficiente aggiungere semplicemente l'operatore ref, come illustrato nella prima chiamata a Increment nel codice seguente. Poiché la creazione di una cella di riferimento comporta la creazione di una copia del valore sottostante, tramite la prima chiamata viene solo incrementato un valore temporaneo.
type Incrementor(z) =
member this.Increment(i : int byref) =
i <- i + z
let incrementor = new Incrementor(1)
let mutable x = 10
// Not recommended: Does not actually increment the variable.
incrementor.Increment(ref x)
// Prints 10.
printfn "%d" x
let mutable y = 10
incrementor.Increment(&y)
// Prints 11.
printfn "%d" y
let refInt = ref 10
incrementor.Increment(refInt)
// Prints 11.
printfn "%d" !refInt
È possibile utilizzare una tupla come valore restituito per archiviare qualsiasi parametro out nei metodi della libreria .NET. In alternativa, è possibile trattare il parametro out come parametro byref. Nell'esempio di codice seguente vengono illustrati entrambi i metodi.
// TryParse has a second parameter that is an out parameter
// of type System.DateTime.
let (b, dt) = System.DateTime.TryParse("12-20-04 12:21:00")
printfn "%b %A" b dt
// The same call, using an address of operator.
let mutable dt2 = System.DateTime.Now
let b2 = System.DateTime.TryParse("12-20-04 12:21:00", &dt2)
printfn "%b %A" b2 dt2
Matrici di parametri
Occasionalmente, è necessario definire una funzione che accetta un numero arbitrario di parametri di tipo eterogeneo. Non sarebbe pratico creare tutti i possibili metodi di overload per rendere conto di tutti i tipi che potrebbero venire utilizzati. La piattaforma .NET fornisce supporto per tali metodi tramite la funzionalità costituita dalla matrice di parametri. È possibile fornire un metodo che accetta una matrice di parametri nella firma con un numero arbitrario di parametri. I parametri vengono inseriti in una matrice. Il tipo di elementi della matrice determina i tipi di parametri che possono essere passati alla funzione. Se si definisce la matrice di parametri con Object come tipo di elemento, il codice client può passare valori di qualsiasi tipo.
In F# le matrici di parametri possono essere definite solo nei metodi e non possono essere utilizzate in funzioni autonome o in funzioni definite nei moduli.
È possibile definire una matrice di parametri utilizzando l'attributo ParamArray. L'attributo ParamArray può essere applicato solo all'ultimo parametro.
Nel codice seguente vengono illustrate sia la chiamata a un metodo .NET che accetta una matrice di parametri che la definizione di un tipo in F# che dispone di un metodo che accetta una matrice di parametri.
open System
type X() =
member this.F([<ParamArray>] args: Object[]) =
for arg in args do
printfn "%A" arg
[<EntryPoint>]
let main _ =
// call a .NET method that takes a parameter array, passing values of various types
Console.WriteLine("a {0} {1} {2} {3} {4}", 1, 10.0, "Hello world", 1u, true)
let xobj = new X()
// call an F# method that takes a parameter array, passing values of various types
xobj.F("a", 1, 10.0, "Hello world", 1u, true)
0
Quando eseguito in un progetto, l'output dell'esempio di codice precedente è simile al seguente:
a 1 10 Hello world 1 True
"a"
1
10.0
"Hello world"
1u
true