Parametry i argumenty
W tym temacie opisano obsługę języka do definiowania parametrów i przekazywania argumentów do funkcji, metod i właściwości. Zawiera on informacje o sposobie przekazywania przez odwołanie oraz sposobie definiowania i używania metod, które mogą przyjmować zmienną liczbę argumentów.
Parametry i argumenty
Termin parametr służy do opisywania nazw wartości, które mają zostać dostarczone. Argument terminu jest używany dla wartości podanych dla każdego parametru.
Parametry można określić w postaci krotki lub curried lub w jakiejś kombinacji tych dwóch. Argumenty można przekazać przy użyciu jawnej nazwy parametru. Parametry metod można określić jako opcjonalne i przy użyciu wartości domyślnej.
Wzorce parametrów
Parametry dostarczane do funkcji i metod to, ogólnie rzecz biorąc, wzorce oddzielone spacjami. Oznacza to, że w zasadzie każdy z wzorców opisanych w wyrażeniach dopasowania może być używany na liście parametrów dla funkcji lub elementu członkowskiego.
Metody zwykle używają formy krotki przekazywania argumentów. Pozwala to uzyskać jaśniejszy wynik z perspektywy innych języków platformy .NET, ponieważ formularz krotki pasuje do sposobu przekazywania argumentów w metodach platformy .NET.
Formularz curried jest najczęściej używany z funkcjami utworzonymi za pomocą let
powiązań.
Poniższy pseudokod przedstawia przykłady krotek i argumentów curried.
// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...
Formularze połączone są możliwe, gdy niektóre argumenty znajdują się w krotkach, a niektóre nie.
let function2 param1 (param2a, param2b) param3 = ...
Inne wzorce mogą być również używane na listach parametrów, ale jeśli wzorzec parametru nie pasuje do wszystkich możliwych danych wejściowych, w czasie wykonywania może istnieć niekompletne dopasowanie. Wyjątek MatchFailureException
jest generowany, gdy wartość argumentu nie jest zgodna z wzorcami określonymi na liście parametrów. Kompilator wyświetla ostrzeżenie, gdy wzorzec parametru zezwala na niekompletne dopasowania. Co najmniej jeden inny wzorzec jest często przydatny w przypadku list parametrów i jest to wzorzec wieloznaczny. Wzorzec z symbolami wieloznacznymi na liście parametrów jest używany, gdy po prostu chcesz zignorować wszystkie podane argumenty. Poniższy kod ilustruje użycie wzorca symboli wieloznacznych na liście argumentów.
let makeList _ = [ for i in 1 .. 100 -> i * i ]
// The arguments 100 and 200 are ignored.
let list1 = makeList 100
let list2 = makeList 200
Wzorzec z symbolami wieloznacznymi może być przydatny, gdy nie potrzebujesz argumentów przekazanych, takich jak w głównym punkcie wejścia do programu, gdy nie interesuje Cię argumenty wiersza polecenia, które są zwykle dostarczane jako tablica ciągów, jak w poniższym kodzie.
[<EntryPoint>]
let main _ =
printfn "Entry point!"
0
Inne wzorce, które są czasami używane w argumentach, to as
wzorzec, a wzorce identyfikatorów skojarzone z dyskryminowanymi związkami i aktywnymi wzorcami. W następujący sposób można użyć wzorca unii dyskryminowanej z jedną literą.
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
Dane wyjściowe są następujące:
Data begins at 0 and ends at 4 in string Et tu, Brute?
Et tu
Aktywne wzorce mogą być przydatne jako parametry, na przykład podczas przekształcania argumentu w żądany format, jak w poniższym przykładzie:
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
Za pomocą as
wzorca można przechowywać dopasowaną wartość jako wartość lokalną, jak pokazano w poniższym wierszu kodu.
let GetSubstring2 (Slice(p0, p1, text) as s) = s
Innym wzorcem używanym od czasu do czasu jest funkcja, która pozostawia ostatni argument bez nazwy, podając jako treść funkcji wyrażenie lambda, które natychmiast wykonuje dopasowanie wzorca na niejawnym argumencie. Przykładem jest następujący wiersz kodu.
let isNil = function [] -> true | _::_ -> false
Ten kod definiuje funkcję, która przyjmuje listę ogólną i zwraca true
wartość, jeśli lista jest pusta, a false
w przeciwnym razie. Użycie takich technik może utrudnić odczytywanie kodu.
Czasami wzorce obejmujące niekompletne dopasowania są przydatne, na przykład jeśli wiesz, że listy w programie mają tylko trzy elementy, możesz użyć wzorca takiego jak na poniższej liście parametrów.
let sum [a; b; c;] = a + b + c
Użycie wzorców, które mają niekompletne dopasowania, jest najlepiej zarezerwowane do szybkiego tworzenia prototypów i innych zastosowań tymczasowych. Kompilator wyświetli ostrzeżenie dotyczące takiego kodu. Takie wzorce nie mogą obejmować ogólnego przypadku wszystkich możliwych danych wejściowych i dlatego nie są odpowiednie dla interfejsów API składników.
Nazwane argumenty
Argumenty metod można określić według pozycji na liście argumentów rozdzielanych przecinkami lub mogą być przekazywane do metody jawnie, podając nazwę, a następnie znak równości i wartość do przekazania. Jeśli zostanie określona przez podanie nazwy, może być wyświetlana w innej kolejności niż używana w deklaracji.
Nazwane argumenty mogą zwiększyć czytelność kodu i bardziej dostosować się do niektórych typów zmian w interfejsie API, takich jak zmiana kolejności parametrów metody.
Nazwane argumenty są dozwolone tylko dla metod, a nie dla let
-bound functions, wartości funkcji lub wyrażeń lambda.
W poniższym przykładzie kodu pokazano użycie nazwanych argumentów.
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)
W wywołaniu konstruktora klasy można ustawić wartości właściwości klasy przy użyciu składni podobnej do nazwanych argumentów. W poniższym przykładzie przedstawiono tę składnię.
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)
Aby uzyskać więcej informacji, zobacz Konstruktory (F#).
Ta sama technika, przeznaczona do wywoływania metod ustawiających właściwości, ma również zastosowanie do dowolnej metody zwracania obiektów (np. metod fabrycznych):
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
Należy pamiętać, że członkowie mogą wykonać dowolną pracę, składnia jest w rzeczywistości krótką ręką w celu wywołania metod ustawiających właściwości przed zwróceniem wartości końcowej.
Parametry opcjonalne
Możesz określić opcjonalny parametr dla metody, używając znaku zapytania przed nazwą parametru. Z perspektywy obiektu wywoływanego parametry opcjonalne są interpretowane jako typ opcji języka F#, dzięki czemu można wykonywać zapytania względem nich w zwykły sposób, w jaki typy opcji są odpytywane, przy użyciu match
wyrażenia z elementami Some
i None
. Parametry opcjonalne są dozwolone tylko dla elementów członkowskich, a nie dla funkcji utworzonych przy użyciu let
powiązań.
Istniejące wartości opcjonalne można przekazać do metody według nazwy parametru, takiej jak ?arg=None
lub ?arg=Some(3)
lub ?arg=arg
. Może to być przydatne podczas tworzenia metody, która przekazuje opcjonalne argumenty do innej metody.
Można również użyć funkcji defaultArg
, która ustawia wartość domyślną opcjonalnego argumentu. Funkcja defaultArg
przyjmuje opcjonalny parametr jako pierwszy argument i wartość domyślną jako drugą.
Poniższy przykład ilustruje użycie parametrów opcjonalnych.
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)
Dane wyjściowe są następujące:
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
Na potrzeby międzyoperacyjności języka C# i Visual Basic można użyć atrybutów [<Optional; DefaultParameterValue<(...)>]
w języku F#, aby osoby wywołujące zobaczyły argument jako opcjonalny. Jest to odpowiednik definiowania argumentu jako opcjonalnego w języku C#, jak w pliku MyMethod(int i = 3)
.
open System
open System.Runtime.InteropServices
type C =
static member Foo([<Optional; DefaultParameterValue("Hello world")>] message) =
printfn $"{message}"
Można również określić nowy obiekt jako domyślną wartość parametru. Na przykład element Foo
członkowski może mieć opcjonalny element CancellationToken
jako dane wejściowe:
open System.Threading
open System.Runtime.InteropServices
type C =
static member Foo([<Optional; DefaultParameterValue(CancellationToken())>] ct: CancellationToken) =
printfn $"{ct}"
Wartość podana jako argument DefaultParameterValue
musi być zgodna z typem parametru. Na przykład następujące informacje są niedozwolone:
type C =
static member Wrong([<Optional; DefaultParameterValue("string")>] i:int) = ()
W takim przypadku kompilator generuje ostrzeżenie i całkowicie zignoruje oba atrybuty. Należy pamiętać, że wartość null
domyślna musi być adnotacją typu, ponieważ w przeciwnym razie kompilator wywnioskuje niewłaściwy typ, tj. [<Optional; DefaultParameterValue(null:obj)>] o:obj
.
Przekazywanie według odwołania
Przekazywanie wartości języka F# według odwołania obejmuje elementy byrefs, które są typami zarządzanych wskaźników. Wskazówki dotyczące typu, który ma być używany, jest następujący:
- Użyj
inref<'T>
polecenia , jeśli musisz tylko odczytać wskaźnik. - Użyj
outref<'T>
polecenia , jeśli musisz zapisywać tylko w wskaźniku. - Użyj
byref<'T>
polecenia , jeśli musisz odczytywać dane z i zapisywać w wskaźniku.
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
Ponieważ parametr jest wskaźnikiem, a wartość jest modyfikowalna, wszelkie zmiany wartości są zachowywane po wykonaniu funkcji.
Możesz użyć krotki jako wartości zwracanej do przechowywania dowolnych out
parametrów w metodach biblioteki platformy .NET. Alternatywnie można traktować out
parametr jako byref
parametr. Poniższy przykład kodu ilustruje oba sposoby.
// 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
Parameter — Tablice
Czasami konieczne jest zdefiniowanie funkcji, która przyjmuje dowolną liczbę parametrów typu heterogenicznego. Nie byłoby praktyczne utworzenie wszystkich możliwych przeciążonych metod do uwzględnienia wszystkich typów, które mogą być używane. Implementacje platformy .NET zapewniają obsługę takich metod za pośrednictwem funkcji tablicy parametrów. Metoda, która przyjmuje tablicę parametrów w podpisie, może być dostarczana z dowolną liczbą parametrów. Parametry są umieszczane w tablicy. Typ elementów tablicy określa typy parametrów, które można przekazać do funkcji. Jeśli zdefiniujesz tablicę System.Object
parametrów z typem elementu, kod klienta może przekazać wartości dowolnego typu.
W języku F# tablice parametrów można definiować tylko w metodach. Nie można ich używać w funkcjach autonomicznych ani funkcjach zdefiniowanych w modułach.
Tablicę parametrów definiuje się przy użyciu atrybutu ParamArray
. Atrybut ParamArray
można zastosować tylko do ostatniego parametru.
Poniższy kod ilustruje zarówno wywołanie metody .NET, która przyjmuje tablicę parametrów, jak i definicję typu w języku F#, który ma metodę, która przyjmuje tablicę parametrów.
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
Po uruchomieniu w projekcie dane wyjściowe poprzedniego kodu są następujące:
a 1 10 Hello world 1 True
"a"
1
10.0
"Hello world"
1u
true