Parametri e argomenti

In questo argomento viene descritto il supporto del linguaggio per la definizione dei parametri e il passaggio di argomenti a funzioni, metodi e proprietà. Include informazioni su come passare per riferimento, nonché definire e usare metodi che possono accettare un numero variabile di argomenti.

Parametri e argomenti

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

I parametri possono essere specificati in formato tupla o currying oppure con una combinazione dei due formati. È possibile passare argomenti usando un nome di parametro esplicito. È possibile specificare i parametri dei metodi come facoltativi e assegnare loro un valore predefinito.

Criteri dei parametri

I parametri forniti a funzioni e metodi sono in genere criteri delimitati da spazi. Questo significa che, in linea di principio, qualsiasi criterio descritto in Espressioni di corrispondenza può essere usato in un elenco di parametri per una funzione o un membro.

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

Il formato currying viene per lo più usato con le funzioni create tramite associazioni let.

Lo pseudocodice seguente mostra esempi di argomenti passati con formato tupla e currying.

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

È possibile usare una combinazione dei due formati quando alcuni argomenti vengono passati in formato tupla e altri no.

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

Negli elenchi di parametri è possibile usare anche altri criteri, ma se il criterio 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 criteri specificati nell'elenco di parametri. Il compilatore genera un avviso quando un criterio di parametro consente corrispondenze incomplete. Almeno un altro criterio generalmente utile per gli elenchi di parametri è quello con caratteri jolly. Il criterio con caratteri jolly si usa in un elenco di parametri quando si vogliono ignorare tutti gli argomenti specificati. Nell'esempio di codice seguente viene illustrato l'utilizzo 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 se non sono necessari gli argomenti passati, ad esempio nel punto di ingresso principale di un programma, quando non si è interessati agli argomenti della riga di comando che vengono in genere specificati come matrice di stringhe, come nel codice seguente.

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

Altri criteri usati talvolta negli argomenti sono il criterio as e i criteri di identificatore associati a unioni discriminate e criteri attivi. È possibile usare il criterio 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 criteri 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 criterio 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 criterio che viene usato occasionalmente è una funzione che lascia senza nome l'ultimo argomento fornendo, come corpo della funzione, un'espressione lambda che esegue immediatamente una corrispondenza di criteri nell'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 più difficoltosa la lettura del codice.

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

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

È comunque consigliabile limitare l'uso dei criteri con corrispondenze incomplete alla creazione rapida di prototipi e ad altri utilizzi temporanei. Il compilatore genera un avviso per tale codice. Tali criteri non possono comprendere il caso generale di tutti gli input possibili e non sono quindi adatti per le API dei componenti.

Argomenti denominati

È possibile specificare gli argomenti per i metodi in base alla posizione in un elenco di argomenti delimitati da virgole oppure passarli a un metodo in modo esplicito specificandone il nome, seguito da un segno di uguale e dal valore da passare. Se vengono specificati tramite 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 le funzioni associate a let, i valori di funzione o le espressioni lambda.

Nell'esempio di codice seguente viene illustrato l'utilizzo degli 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 che viene usata per 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 operazione arbitraria. La sintassi consente in effetti di chiamare rapidamente i setter di proprietà prima di restituire il valore finale.

Parametri facoltativi

È possibile specificare un parametro facoltativo per un metodo anteponendo un punto interrogativo al nome del parametro. Dal punto di vista del metodo chiamato, i parametri facoltativi vengono interpretati come tipo di opzione F#, quindi è possibile eseguire query relative a tali parametri normalmente come se si trattasse di un tipo di opzione, usando un'espressione match con Some e None. I parametri facoltativi sono consentiti solo nei membri, non nelle funzioni create tramite associazioni let.

È possibile passare valori facoltativi esistenti al metodo in base al nome del parametro, ad esempio ?arg=None, ?arg=Some(3) o ?arg=arg. Questo può essere utile quando si crea 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 funzione defaultArg accetta il parametro facoltativo come primo argomento e il valore predefinito come secondo.

Nell'esempio seguente viene illustrato l'uso 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)
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à di C# e Visual Basic è possibile usare gli attributi [<Optional; DefaultParameterValue<(...)>] in F#, in modo che i metodi chiamanti considerino un argomento come facoltativo. Questo 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 membro Foo potrebbe avere un parametro CancellationToken facoltativo 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, quanto segue non è consentito:

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 predefinito null deve essere annotato come tipo. In caso contrario il compilatore deduce il tipo errato, ovvero [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Passaggio per riferimento

Il passaggio di un valore F# per riferimento implica l'uso di byref, che sono tipi di puntatore gestiti. Di seguito sono disponibili indicazioni utili relative al tipo da usare:

  • Usare inref<'T> se è necessario solo leggere il puntatore.
  • Usare outref<'T> se è necessario solo scrivere nel puntatore.
  • Usare byref<'T> se è necessario sia leggere il puntatore sia 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 i parametri out nei metodi della libreria .NET. In alternativa, è possibile considerare il parametro out come parametro byref. 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 accetti un numero arbitrario di parametri di tipo diverso. Non sarebbe pratico creare tutti i possibili metodi di overload per tutti i tipi che potrebbero essere utilizzati. Le implementazioni di .NET forniscono supporto per tali metodi tramite la funzionalità della matrice di parametri. Per un metodo che accetta una matrice di parametri nella propria firma è possibile specificare 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 indicando System.Object come tipo di elemento, il codice client può quindi passare valori di qualsiasi tipo.

In F# le matrici di parametri possono essere definite solo nei metodi. Non possono essere usate in funzioni autonome o definite in moduli.

Per definire una matrice di parametri, si usa 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 sia 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