Parameters en argumenten

In dit onderwerp wordt taalondersteuning beschreven voor het definiëren van parameters en het doorgeven van argumenten aan functies, methoden en eigenschappen. Het bevat informatie over het doorgeven van verwijzingen en het definiëren en gebruiken van methoden die een variabel aantal argumenten kunnen aannemen.

Parameters en argumenten

De termparameter wordt gebruikt om de namen te beschrijven voor waarden die naar verwachting worden opgegeven. Het termargument wordt gebruikt voor de waarden die zijn opgegeven voor elke parameter.

Parameters kunnen worden opgegeven in tupel- of curriede vorm, of in een combinatie van de twee. U kunt argumenten doorgeven met behulp van een expliciete parameternaam. Parameters van methoden kunnen worden opgegeven als optioneel en gegeven een standaardwaarde.

Parameterpatronen

Parameters die worden geleverd aan functies en methoden, worden in het algemeen gescheiden door spaties. Dit betekent dat in principe een van de patronen die worden beschreven in Match Expressions , kan worden gebruikt in een parameterlijst voor een functie of lid.

Methoden gebruiken meestal de tuple-vorm van het doorgeven van argumenten. Dit levert een duidelijker resultaat op vanuit het perspectief van andere .NET-talen, omdat het tuple-formulier overeenkomt met de manier waarop argumenten worden doorgegeven in .NET-methoden.

De curriede vorm wordt meestal gebruikt met functies die zijn gemaakt met behulp van let bindingen.

In de volgende pseudocode ziet u voorbeelden van tuple- en curriede argumenten.

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

Gecombineerde vormen zijn mogelijk wanneer sommige argumenten zich in tuples bevinden en sommige niet.

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

Andere patronen kunnen ook worden gebruikt in parameterlijsten, maar als het parameterpatroon niet overeenkomt met alle mogelijke invoer, is er mogelijk een onvolledige overeenkomst tijdens de runtime. De uitzondering MatchFailureException wordt gegenereerd wanneer de waarde van een argument niet overeenkomt met de patronen die zijn opgegeven in de parameterlijst. De compiler geeft een waarschuwing wanneer een parameterpatroon onvolledige overeenkomsten toestaat. Ten minste één ander patroon is meestal handig voor parameterlijsten en dat is het jokertekenpatroon. U gebruikt het jokertekenpatroon in een parameterlijst wanneer u alleen argumenten wilt negeren die worden opgegeven. De volgende code illustreert het gebruik van het jokertekenpatroon in een lijst met argumenten.

let makeList _ = [ for i in 1 .. 100 -> i * i ]
// The arguments 100 and 200 are ignored.
let list1 = makeList 100
let list2 = makeList 200

Het jokertekenpatroon kan handig zijn wanneer u de argumenten die worden doorgegeven niet nodig hebt, zoals in het hoofdinvoerpunt van een programma, wanneer u niet geïnteresseerd bent in de opdrachtregelargumenten die normaal gesproken worden opgegeven als een tekenreeksmatrix, zoals in de volgende code.

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

Andere patronen die soms in argumenten worden gebruikt, zijn het as patroon en id-patronen die zijn gekoppeld aan gediscrimineerde samenvoegingen en actieve patronen. U kunt het gediscrimineerde union-patroon in één geval als volgt gebruiken.

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

De uitvoer is als volgt.

Data begins at 0 and ends at 4 in string Et tu, Brute?
Et tu

Actieve patronen kunnen handig zijn als parameters, bijvoorbeeld bij het transformeren van een argument in een gewenste indeling, zoals in het volgende voorbeeld:

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

U kunt het as patroon gebruiken om een overeenkomende waarde op te slaan als een lokale waarde, zoals wordt weergegeven in de volgende coderegel.

let GetSubstring2 (Slice(p0, p1, text) as s) = s

Een ander patroon dat af en toe wordt gebruikt, is een functie die het laatste argument ongenaamd laat door, als hoofdtekst van de functie, een lambda-expressie op te geven die onmiddellijk een patroonovereenkomst uitvoert op het impliciete argument. Een voorbeeld hiervan is de volgende coderegel.

let isNil = function [] -> true | _::_ -> false

Deze code definieert een functie die een algemene lijst gebruikt en retourneert true als de lijst leeg is en false anders. Het gebruik van dergelijke technieken kan code moeilijker leesbaar maken.

Soms zijn patronen met onvolledige overeenkomsten handig, bijvoorbeeld als u weet dat de lijsten in uw programma slechts drie elementen bevatten, kunt u een patroon zoals het volgende gebruiken in een parameterlijst.

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

Het gebruik van patronen met onvolledige overeenkomsten is het beste gereserveerd voor snelle prototypen en andere tijdelijke toepassingen. De compiler geeft een waarschuwing voor dergelijke code uit. Dergelijke patronen kunnen het algemene geval van alle mogelijke invoer niet dekken en zijn daarom niet geschikt voor onderdeel-API's.

Benoemde argumenten

Argumenten voor methoden kunnen worden opgegeven op positie in een lijst met door komma's gescheiden argumenten of ze kunnen expliciet worden doorgegeven aan een methode door de naam op te geven, gevolgd door een gelijkteken en de waarde die moet worden doorgegeven. Indien opgegeven door de naam op te geven, kunnen ze in een andere volgorde worden weergegeven dan die in de declaratie wordt gebruikt.

Benoemde argumenten kunnen code beter leesbaar en beter aanpasbaar maken aan bepaalde typen wijzigingen in de API, zoals het opnieuw ordenen van methodeparameters.

Benoemde argumenten zijn alleen toegestaan voor methoden, niet voor let-gebonden functies, functiewaarden of lambda-expressies.

In het volgende codevoorbeeld ziet u het gebruik van benoemde argumenten.

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 een aanroep naar een klasseconstructor kunt u de waarden van eigenschappen van de klasse instellen met behulp van een syntaxis die vergelijkbaar is met die van benoemde argumenten. In het volgende voorbeeld ziet u deze syntaxis.

 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)

Zie Constructors (F#) voor meer informatie.

Dezelfde techniek, bedoeld om eigenschapssetters aan te roepen, is ook van toepassing op elke methode die objecten retourneert (zoals fabrieksmethoden):

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

Houd er rekening mee dat deze leden willekeurige werkzaamheden kunnen uitvoeren. De syntaxis is in feite een korte hand om eigenschapssetters aan te roepen voordat de uiteindelijke waarde wordt geretourneerd.

Optionele parameters

U kunt een optionele parameter voor een methode opgeven met behulp van een vraagteken vóór de parameternaam. Optionele parameters worden geïnterpreteerd als het F#-optietype, zodat u query's kunt uitvoeren op de normale manier waarop de optietypen worden opgevraagd, met behulp van een match expressie met Some en None. Optionele parameters zijn alleen toegestaan voor leden, niet voor functies die zijn gemaakt met behulp van let bindingen.

U kunt bestaande optionele waarden doorgeven aan de methode op parameternaam, zoals ?arg=None of ?arg=arg?arg=Some(3) . Dit kan handig zijn bij het bouwen van een methode die optionele argumenten doorgeeft aan een andere methode.

U kunt ook een functie defaultArggebruiken, waarmee een standaardwaarde van een optioneel argument wordt ingesteld. De defaultArg functie gebruikt de optionele parameter als het eerste argument en de standaardwaarde als de tweede.

In het volgende voorbeeld ziet u het gebruik van optionele parameters.

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)

De uitvoer is als volgt.

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

Voor de interop tussen C# en Visual Basic kunt u de kenmerken [<Optional; DefaultParameterValue<(...)>] in F# gebruiken, zodat bellers een argument zien als optioneel. Dit is gelijk aan het definiëren van het argument als optioneel in C# zoals in MyMethod(int i = 3).

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

U kunt ook een nieuw object opgeven als een standaardparameterwaarde. Het lid kan bijvoorbeeld Foo een optionele CancellationToken invoer hebben als invoer:

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

De waarde die als argument DefaultParameterValue wordt opgegeven, moet overeenkomen met het type parameter. Het volgende is bijvoorbeeld niet toegestaan:

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

In dit geval genereert de compiler een waarschuwing en worden beide kenmerken helemaal genegeerd. Houd er rekening mee dat de standaardwaarde null moet worden getypt, omdat anders het verkeerde type wordt afgeleid door de compiler, dat wil [<Optional; DefaultParameterValue(null:obj)>] o:objzeggen.

Doorgeven per verwijzing

Het doorgeven van een F#-waarde per verwijzing omvat byrefs, die beheerde aanwijzertypen zijn. Richtlijnen voor het type dat u wilt gebruiken:

  • Gebruik inref<'T> deze optie als u de aanwijzer alleen hoeft te lezen.
  • Gebruik outref<'T> deze optie als u alleen naar de aanwijzer hoeft te schrijven.
  • Gebruik byref<'T> deze optie als u zowel moet lezen van als schrijven naar de aanwijzer.
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

Omdat de parameter een aanwijzer is en de waarde onveranderbaar is, blijven wijzigingen in de waarde behouden na de uitvoering van de functie.

U kunt een tuple als retourwaarde gebruiken om parameters out op te slaan in .NET-bibliotheekmethoden. U kunt de out parameter ook behandelen als een byref parameter. In het volgende codevoorbeeld ziet u beide manieren.

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

Parametermatrices

Af en toe is het noodzakelijk om een functie te definiëren die een willekeurig aantal parameters van het heterogene type gebruikt. Het zou niet praktisch zijn om alle mogelijke overbelaste methoden te maken om rekening te houden met alle typen die kunnen worden gebruikt. De .NET-implementaties bieden ondersteuning voor dergelijke methoden via de parametermatrixfunctie. Een methode die een parametermatrix in de handtekening gebruikt, kan worden geleverd met een willekeurig aantal parameters. De parameters worden in een matrix geplaatst. Het type van de matrixelementen bepaalt de parametertypen die aan de functie kunnen worden doorgegeven. Als u de parametermatrix System.Object definieert als het elementtype, kan clientcode waarden van elk type doorgeven.

In F# kunnen parametermatrices alleen worden gedefinieerd in methoden. Ze kunnen niet worden gebruikt in zelfstandige functies of functies die zijn gedefinieerd in modules.

U definieert een parametermatrix met behulp van het ParamArray kenmerk. Het ParamArray kenmerk kan alleen worden toegepast op de laatste parameter.

De volgende code illustreert zowel het aanroepen van een .NET-methode die een parametermatrix gebruikt als de definitie van een type in F# met een methode die een parametermatrix gebruikt.

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

Wanneer u in een project wordt uitgevoerd, is de uitvoer van de vorige code als volgt:

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

Zie ook