Dela via


Parametrar och argument

Det här avsnittet beskriver språkstöd för att definiera parametrar och skicka argument till funktioner, metoder och egenskaper. Den innehåller information om hur du skickar med referens och hur du definierar och använder metoder som kan ta ett variabelt antal argument.

Parametrar och argument

Termparametern används för att beskriva namnen på de värden som förväntas anges. Termargumentet används för de värden som anges för varje parameter.

Parametrar kan anges i tuppeln eller i curryform, eller i någon kombination av de två. Du kan skicka argument med hjälp av ett explicit parameternamn. Parametrar för metoder kan anges som valfria och ges ett standardvärde.

Parametermönster

Parametrar som tillhandahålls till funktioner och metoder är i allmänhet mönster avgränsade med blanksteg. Det innebär att i princip alla mönster som beskrivs i Match Expressions kan användas i en parameterlista för en funktion eller medlem.

Metoder använder vanligtvis tuppelns form av skickande argument. Detta ger ett tydligare resultat från andra .NET-språk eftersom tuppelns formulär matchar hur argument skickas i .NET-metoder.

Den curryform används oftast med funktioner som skapats med hjälp let av bindningar.

Följande pseudokod visar exempel på tupplar och curryargument.

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

Kombinerade formulär är möjliga när vissa argument finns i tupplar och vissa inte är det.

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

Andra mönster kan också användas i parameterlistor, men om parametermönstret inte matchar alla möjliga indata kan det finnas en ofullständig matchning vid körning. Undantaget MatchFailureException genereras när värdet för ett argument inte matchar de mönster som anges i parameterlistan. Kompilatorn utfärdar en varning när ett parametermönster tillåter ofullständiga matchningar. Minst ett annat mönster är ofta användbart för parameterlistor, och det är jokertecknets mönster. Du använder jokerteckenmönstret i en parameterlista när du bara vill ignorera argument som anges. Följande kod illustrerar användningen av jokerteckenmönstret i en argumentlista.

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

Jokerteckenmönstret kan vara användbart när du inte behöver argumenten som skickas in, till exempel i huvudinmatningspunkten till ett program, när du inte är intresserad av kommandoradsargumenten som normalt tillhandahålls som en strängmatris, som i följande kod.

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

Andra mönster som ibland används i argument är as mönstret och identifierarmönster som är associerade med diskriminerade fackföreningar och aktiva mönster. Du kan använda mönstret för en enda ärendediskriminerad union på följande sätt.

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

Utdata är följande.

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

Aktiva mönster kan vara användbara som parametrar, till exempel när du omvandlar ett argument till ett önskat format, som i följande exempel:

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

Du kan använda as mönstret för att lagra ett matchat värde som ett lokalt värde, vilket visas i följande kodrad.

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

Ett annat mönster som används ibland är en funktion som lämnar det sista argumentet namnlöst genom att som funktionstext tillhandahålla ett lambda-uttryck som omedelbart utför en mönstermatchning på det implicita argumentet. Ett exempel på detta är följande kodrad.

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

Den här koden definierar en funktion som tar en allmän lista och returnerar true om listan är tom och false annars. Användningen av sådana tekniker kan göra det svårare att läsa kod.

Ibland är mönster som omfattar ofullständiga matchningar användbara, till exempel om du vet att listorna i programmet bara har tre element, kan du använda ett mönster som följande i en parameterlista.

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

Användning av mönster som har ofullständiga matchningar är bäst reserverad för snabb prototyper och andra tillfälliga användningsområden. Kompilatorn utfärdar en varning för sådan kod. Sådana mönster kan inte täcka det allmänna fallet för alla möjliga indata och är därför inte lämpliga för komponent-API:er.

Namngivna argument

Argument för metoder kan anges efter position i en kommaavgränsad argumentlista, eller så kan de skickas till en metod explicit genom att ange namnet, följt av ett likhetstecken och det värde som ska skickas in. Om det anges genom att ange namnet kan de visas i en annan ordning än den som används i deklarationen.

Namngivna argument kan göra koden mer läsbar och mer anpassningsbar för vissa typer av ändringar i API:et, till exempel en omordning av metodparametrar.

Namngivna argument tillåts endast för metoder, inte för let-bound-funktioner, funktionsvärden eller lambda-uttryck.

Följande kodexempel visar användningen av namngivna argument.

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)

I ett anrop till en klasskonstruktor kan du ange värdena för egenskaperna för klassen med hjälp av en syntax som liknar den för namngivna argument. I följande exempel visas den här syntaxen.

 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)

Mer information finns i Konstruktorer (F#).

Samma teknik, som är avsedd att anropa egenskapsuppsättningar, gäller även för alla objektreturmetoder (till exempel fabriksmetoder):

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

Observera att dessa medlemmar kan utföra godtyckligt arbete, syntaxen är i själva verket en kort hand för att anropa egenskapsuppsättningar innan det slutliga värdet returneras.

Valfria parametrar

Du kan ange en valfri parameter för en metod med hjälp av ett frågetecken framför parameternamnet. Från samtalsmottagarens perspektiv tolkas valfria parametrar som alternativtypen F# så att du kan köra frågor mot dem på det vanliga sätt som alternativtyper efterfrågas genom att använda ett match uttryck med Some och None. Valfria parametrar tillåts endast för medlemmar, inte för funktioner som skapas med hjälp let av bindningar.

Du kan skicka befintliga valfria värden till metoden efter parameternamn, till exempel ?arg=None eller ?arg=arg?arg=Some(3) . Detta kan vara användbart när du skapar en metod som skickar valfria argument till en annan metod.

Du kan också använda en funktion defaultArg, som anger ett standardvärde för ett valfritt argument. Funktionen defaultArg tar den valfria parametern som det första argumentet och standardvärdet som det andra.

I följande exempel visas användningen av valfria parametrar.

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)

Utdata är följande.

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

För C# och Visual Basic-interop kan du använda attributen [<Optional; DefaultParameterValue<(...)>] i F#, så att anropare ser ett argument som valfritt. Detta motsvarar att definiera argumentet som valfritt i C# som i MyMethod(int i = 3).

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

Du kan också ange ett nytt objekt som standardparametervärde. Medlemmen kan till exempel Foo ha ett valfritt CancellationToken som indata i stället:

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

Värdet som anges som argument för DefaultParameterValue måste matcha parametertypen. Följande är till exempel inte tillåtet:

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

I det här fallet genererar kompilatorn en varning och ignorerar båda attributen helt och hållet. Observera att standardvärdet null måste vara typanteckning, eftersom kompilatorn annars härleder fel typ, dvs. [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Skicka med referens

Att skicka ett F#-värde med referens omfattar byrefs, som är hanterade pekartyper. Vägledning för vilken typ som ska användas är följande:

  • Använd inref<'T> om du bara behöver läsa pekaren.
  • Använd outref<'T> om du bara behöver skriva till pekaren.
  • Använd byref<'T> om du behöver både läsa från och skriva till pekaren.
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

Eftersom parametern är en pekare och värdet är föränderligt behålls alla ändringar i värdet efter körningen av funktionen.

Du kan använda en tuppeln som ett returvärde för att lagra alla out parametrar i .NET-biblioteksmetoder. Du kan också behandla parametern out som en byref parameter. Följande kodexempel illustrerar båda sätten.

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

Parametermatriser

Ibland är det nödvändigt att definiera en funktion som tar ett godtyckligt antal parametrar av heterogen typ. Det skulle inte vara praktiskt att skapa alla möjliga överlagrade metoder för att ta hänsyn till alla typer som kan användas. .NET-implementeringarna ger stöd för sådana metoder via parametermatrisfunktionen. En metod som tar en parametermatris i sin signatur kan tillhandahållas med ett godtyckligt antal parametrar. Parametrarna placeras i en matris. Typen av matriselement avgör vilka parametertyper som kan skickas till funktionen. Om du definierar parametermatrisen med System.Object som elementtyp kan klientkoden skicka värden av vilken typ som helst.

I F# kan parametermatriser bara definieras i metoder. De kan inte användas i fristående funktioner eller funktioner som definieras i moduler.

Du definierar en parametermatris med hjälp av attributet ParamArray . Attributet ParamArray kan endast tillämpas på den sista parametern.

Följande kod visar både anropa en .NET-metod som tar en parametermatris och definitionen av en typ i F# som har en metod som tar en parametermatris.

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

När du kör i ett projekt är utdata från den tidigare koden följande:

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

Se även