Parametri e argomenti

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 su come passare per riferimento e su come definire e usare metodi che possono accettare un numero variabile di argomenti.

Parametri e argomenti

Il termine parametro viene usato per descrivere i nomi per i valori che devono essere forniti. Il termine argomento viene usato per i valori forniti per ogni parametro.

I parametri possono essere specificati in forma tupla o curried oppure in una combinazione dei due. È possibile passare argomenti usando un nome di parametro esplicito. I parametri dei metodi possono essere specificati come facoltativi e in base a un valore predefinito.

Modelli di parametri

I parametri forniti a funzioni e metodi sono, in generale, modelli separati da spazi. Ciò significa che, in linea di principio, qualsiasi modello descritto in Espressioni di corrispondenza può essere usato in un elenco di parametri per una funzione o un membro.

I metodi usano in genere la forma di tupla di passaggio di argomenti. In questo modo si ottiene un risultato più chiaro dal punto di vista di altri linguaggi .NET perché il formato della tupla corrisponde al modo in cui gli argomenti vengono passati nei metodi .NET.

Il form curried viene spesso usato con le funzioni create usando let le associazioni.

Lo pseudocodice seguente mostra esempi di tupla e argomenti curried.

// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...

I moduli combinati sono possibili quando alcuni argomenti sono in tuple e alcuni non lo sono.

let function2 param1 (param2a, param2b) param3 = ...

Altri modelli possono essere usati anche negli elenchi di parametri, ma se il modello di parametro non corrisponde a tutti gli input possibili, potrebbe verificarsi una corrispondenza incompleta 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 criterio di parametro consente corrispondenze incomplete. Almeno un altro modello è comunemente utile per gli elenchi di parametri e questo è il modello con caratteri jolly. Usare il criterio con caratteri jolly in un elenco di parametri quando si desidera semplicemente ignorare tutti gli argomenti forniti. Il codice seguente illustra l'uso del criterio con caratteri 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 criterio con caratteri jolly può essere utile ogni volta che non sono necessari gli argomenti passati, ad esempio nel punto di ingresso principale a un programma, quando non si è interessati agli argomenti della riga di comando normalmente forniti come matrice di stringhe, come nel codice seguente.

[<EntryPoint>]
let main _ =
    printfn "Entry point!"
    0

Altri modelli usati talvolta negli argomenti sono il as modello e i modelli di identificatore associati a unioni discriminate e modelli attivi. È possibile usare il modello di unione discriminante con maiuscole e minuscole 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 si trasforma un argomento in un formato desiderato, come nell'esempio seguente:

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 usare il as modello 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 criterio usato occasionalmente è una funzione che lascia l'ultimo argomento senza nome fornendo, come corpo della funzione, un'espressione lambda che esegue immediatamente una corrispondenza del criterio sull'argomento implicito. Un esempio è la 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 in caso contrario. L'uso di tali tecniche può rendere il codice più difficile da leggere.

In alcuni casi, i modelli che coinvolgono corrispondenze incomplete sono utili, ad esempio, se si sa che gli elenchi nel programma hanno solo tre elementi, è possibile usare un modello simile al seguente in un elenco di parametri.

let sum [a; b; c;] = a + b + c

L'uso di modelli con corrispondenze incomplete è meglio riservato per la creazione rapida di prototipi e altri usi temporanei. Il compilatore genererà un avviso per tale codice. Tali modelli non possono coprire il caso generale di tutti gli input possibili e pertanto non sono adatti per le API dei componenti.

Argomenti denominati

Gli argomenti per i metodi possono essere specificati dalla posizione in un elenco di argomenti delimitati da virgole oppure possono essere passati a un metodo in modo esplicito specificando il nome, seguito da un segno di uguale e dal valore da passare. Se specificato specificando il nome, possono essere visualizzati in un ordine diverso da quello usato nella dichiarazione.

Gli argomenti denominati possono rendere il codice più leggibile e più adattabile a determinati tipi di modifiche nell'API, ad esempio un riordinamento dei parametri del metodo.

Gli argomenti denominati sono consentiti solo per i metodi, non per letle funzioni associate, i valori delle funzioni o le espressioni lambda.

Nell'esempio di codice seguente viene illustrato l'uso 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 a un costruttore di classe è possibile impostare i valori delle proprietà della classe usando una sintassi simile a quella degli argomenti denominati. Nell'esempio seguente viene illustrata questa sintassi.

 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 altre informazioni, vedere Costruttori (F#).

La stessa tecnica, destinata a chiamare setter di proprietà, si applica anche a qualsiasi metodo che restituisce oggetti (ad esempio i metodi factory):

type Widget() =
    member val Width = 1 with get,set
    member val Height = 1 with get,set

type WidgetFactory =
    static member MakeNewWidget() =
         new Widget()
    static member AdjustWidget(w: Widget) =
         w
let w = WidgetFactory.MakeNewWidget(Width=10)
w.Width // = 10
w.Height // = 1
WidgetFactory.AdjustWidget(w, Height=10)
w.Height // = 10

Si noti che tali membri potrebbero eseguire qualsiasi lavoro arbitrario, la sintassi è in effetti una breve mano per chiamare setter di proprietà prima di restituire il valore finale.

Parametri facoltativi

È possibile specificare un parametro facoltativo per un metodo usando un punto interrogativo davanti al nome del parametro. I parametri facoltativi vengono interpretati come tipo di opzione F#, quindi è possibile eseguirne una query nel modo normale in cui vengono sottoposti a query i tipi di opzione usando un'espressione match con Some e None. I parametri facoltativi sono consentiti solo sui membri, non sulle funzioni create tramite let associazioni.

È possibile passare valori facoltativi esistenti al metodo in base al nome del parametro, ad esempio ?arg=None o ?arg=Some(3) o ?arg=arg. Ciò può essere utile quando si compila un metodo che passa argomenti facoltativi a un altro metodo.

È anche possibile usare una funzione defaultArg, che imposta un valore predefinito di un argomento facoltativo. La defaultArg funzione accetta il parametro facoltativo come primo argomento e il valore predefinito come secondo.

Nell'esempio seguente viene illustrato l'uso di 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)
let conn4 = Connection(?duplex0 = None)
let conn5 = Connection(?duplex0 = Some(Full))

let optionalDuplexValue : option<DuplexType> = Some(Half)
let conn6 = Connection(?duplex0 = optionalDuplexValue)

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
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false

Ai fini dell'interoperabilità C# e Visual Basic è possibile usare gli attributi [<Optional; DefaultParameterValue<(...)>] in F#, in modo che i chiamanti visualizzino un argomento come facoltativo. Equivale a definire l'argomento come facoltativo in C# come in MyMethod(int i = 3).

open System
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue("Hello world")>] message) =
        printfn $"{message}"

È anche possibile specificare un nuovo oggetto come valore di parametro predefinito. Ad esempio, il Foo membro potrebbe avere un facoltativo CancellationToken come input:

open System.Threading
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue(CancellationToken())>] ct: CancellationToken) =
        printfn $"{ct}"

Il valore specificato come argomento per DefaultParameterValue deve corrispondere al tipo del parametro . Ad esempio, non è consentito quanto segue:

type C =
    static member Wrong([<Optional; DefaultParameterValue("string")>] i:int) = ()

In questo caso, il compilatore genera un avviso e ignorerà completamente entrambi gli attributi. Si noti che il valore null predefinito deve essere annotato come tipo, in caso contrario il compilatore deduce il tipo errato, ad esempio [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Passaggio per riferimento

Il passaggio di un valore F# per riferimento comporta byrefs, ovvero tipi di puntatore gestiti. Il materiale sussidiario per il tipo da usare è il seguente:

  • Usare inref<'T> se è sufficiente leggere il puntatore.
  • Usare outref<'T> se è sufficiente scrivere nel puntatore.
  • Usare byref<'T> se è necessario leggere e scrivere nel puntatore.
let example1 (x: inref<int>) = printfn $"It's %d{x}"

let example2 (x: outref<int>) = x <- x + 1

let example3 (x: byref<int>) =
    printfn $"It's %d{x}"
    x <- x + 1

let test () =
    // No need to make it mutable, since it's read-only
    let x = 1
    example1 &x

    // Needs to be mutable, since we write to it
    let mutable y = 2
    example2 &y
    example3 &y // Now 'y' is 3

Poiché il parametro è un puntatore e il valore è modificabile, tutte le modifiche apportate al valore vengono mantenute dopo l'esecuzione della funzione.

È possibile usare una tupla come valore restituito per archiviare tutti out i parametri nei metodi della libreria .NET. In alternativa, è possibile considerare il out parametro come byref parametro. Nell'esempio di codice seguente vengono illustrati entrambi i modi.

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

In alcuni casi è 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 tenere conto di tutti i tipi che potrebbero essere usati. Le implementazioni di .NET forniscono supporto per tali metodi tramite la funzionalità della matrice di parametri. Un metodo che accetta una matrice di parametri nella relativa firma può essere fornito con un numero arbitrario di parametri. I parametri vengono inseriti in una matrice. Il tipo degli elementi della matrice determina i tipi di parametro che possono essere passati alla funzione. Se si definisce la matrice di parametri con System.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. Non possono essere usati nelle funzioni autonome o nelle funzioni definite nei moduli.

Per definire una matrice di parametri, usare l'attributo ParamArray . L'attributo ParamArray può essere applicato solo all'ultimo parametro.

Il codice seguente illustra sia la chiamata di un metodo .NET che accetta una matrice di parametri che la definizione di un tipo in F# con 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 viene eseguito in un progetto, l'output del codice precedente è il seguente:

a 1 10 Hello world 1 True
"a"
1
10.0
"Hello world"
1u
true

Vedi anche