參數和引數

本主題說明對於定義參數以及將引數傳至函式、方法和屬性的語言支援。 其中包含如何以參考方式傳遞的相關資訊,並說明如何定義及使用可採用可變量引數的方法。

參數和引數

參數一詞用來描述預期要提供之值的名稱。 引數一詞則用於為每個參數提供的值。

參數可用 Tuple 或局部調用的形式指定,或以兩者組合的形式指定。 您可以使用明確的參數名稱來傳遞引數。 方法的參數可以指定為選用參數,並給予預設值。

參數模式

提供給函式和方法的參數通常是以空格分隔的模式。 這表示,比對運算式中說明的任何模式基本上都可以用於函式或成員的參數清單中。

方法通常會使用 Tuple 形式的傳遞引數。 從其他 .NET 語言的觀點來看,這樣可以達成更清楚的結果,因為 Tuple 形式符合在 .NET 方法中傳遞引數的方式。

局部調用形式最常用於使用 let 繫結建立的函式。

下列虛擬程式碼顯示 Tuple 和局部調用引數的範例。

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

有些引數是 Tuple 形式,有些不是時,可以採用合併形式。

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

在參數清單中也可以使用其他模式,但如果參數模式不符合所有可能的輸入,則在執行階段可能會有不完全的相符。 當引數的值不符合參數清單中指定的模式時,會產生例外狀況 MatchFailureException。 當參數模式允許不完全的相符時,編譯器會發出警告。 至少還有一個模式應該也適用於參數清單,即萬用字元模式。 如果您想要忽略任何提供的引數,即可在參數清單中使用萬用字元模式。 下列程式碼說明如何在引數清單中使用萬用字元模式。

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

如果時常提供作為字串陣列的命令列引數對您而言並不重要,在不需要傳入引數的情況下,萬用字元模式將可發揮效用 (例如,在程式的主要進入點中),如下列程式碼所示。

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

有時用於引數的其他模式是 as 模式,以及與差別聯集相關聯的識別碼模式和現用模式。 您可以使用單一案例的差別聯集模式,如下所示。

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

輸出如下。

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

現用模式可以作為參數 (例如,將引數轉換為所需的格式時),如下列範例所示:

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

您可以使用 as 模式將相符的值儲存為本機值,如下列程式碼所示。

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

偶爾使用的另一種模式是一個讓最後一個引數保持未命名的函式;具體方式是提供會立即對隱含引數執行模式比對的 Lambda 運算式,作為函式主體。 下列程式碼是其範例之一。

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

此程式碼會定義採用泛型清單的函式,若清單是空的則傳回 true,否則傳回 false。 使用這類技術可讓程式碼更容易讀取。

涉及不完全相符的模式有時是很有用的,例如,如果您知道程式中的清單只有三個元素,您就可以在參數清單中使用如下的模式。

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

涉及不完全相符的模式最好保留給快速原型設計和其他臨時性用途使用。 編譯器會發出這類程式碼的警告。 這類模式無法涵蓋所有可能輸入的一般案例,因此不適用於元件 API。

具名引數

方法的引數可透過在逗號分隔引數清單中的位置來指定,或者,可藉由提供名稱再加上等號和要傳入的值,明確地傳至方法。 如果藉由提供名稱來指定,則可能會以與宣告中使用的不同順序顯示。

具名引數可讓程式碼更容易讀取,且更能順應 API 中特定型別的變更,例如方法參數的重新排序。

具名引數只能用於方法,不適用於 let 繫結函式、函式值或 Lambda 運算式。

下列程式碼範例示範如何使用具名引數。

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)

在呼叫類別建構函式時,您可以使用類似於具名引數的語法來設定類別的屬性值。 下列範例顯示此語法。

 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)

如需詳細資訊,請參閱建構函式 (F#)

同樣用來呼叫屬性 setter 的技術也適用於任何對象傳回方法 (例如 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

請注意,這些成員可以執行任何任意工作,語法實際上是在傳回最終值之前呼叫屬性 setter 的短手。

選擇性參數

您可以在參數名稱前面使用問號,為方法指定選用參數。 選用參數會解譯為 F# 選項型別,因此您可以使用 match 運算式搭配 SomeNone,以查詢選項型別的一般方式加以查詢。 選用參數僅可用在成員上,而不適用於使用 let 繫結建立的函式。

您可以依參數名稱 (例如 ?arg=None?arg=Some(3)?arg=arg) 將現有的選用值傳至方法。 建置將選用引數傳至另一個方法的方法時,此做法會很有用。

您也可以使用設定選用引數預設值的 defaultArg 函式。 defaultArg 函式會以選用參數作為第一個引數,而預設值為第二個引數。

下列範例說明如何使用選用參數。

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)

輸出如下。

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

針對 C# 和 Visual Basic Interop 的用途,您可以在 F# 中使用屬性 [<Optional; DefaultParameterValue<(...)>],讓呼叫端將引數視為選用的。 這相當於在 C# 中將引數定義為選用的,如同在 MyMethod(int i = 3) 中一般。

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

您也可以將新物件指定為預設參數值。 例如,Foo 可以成員改以選用 CancellationToken 作為輸入:

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

指定為 DefaultParameterValue 之引數的值,必須符合參數的型別。 例如,不允許以下操作:

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

在此情況下,編譯器會產生警告,並且完全忽略這兩個屬性。 請注意,預設值 null 必須加上型別註釋,否則編譯器會推斷錯誤的型別,即 [<Optional; DefaultParameterValue(null:obj)>] o:obj

以參考方式傳遞

以參考方式傳遞 F# 值牽涉到 byrefs,此為受控指標型別。 應使用何種型別的指引如下:

  • 如果您只需要讀取指標,請使用 inref<'T>
  • 如果您只需要寫入指標,請使用 outref<'T>
  • 如果您需要讀取和寫入指標,請使用 byref<'T>
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

由於參數是指標,且值是可變的,因此在函式執行後,對值所做的任何變更都會保留。

您可以使用 Tuple 作為傳回值,以將任何 out 參數儲存在 .NET 程式庫方法中。 或者,您可以將 out 參數視為 byref 參數。 下列程式碼範例將說明這兩種方式。

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

參數陣列

有時必須要定義採用任意數目的異質型別參數的函式。 建立所有可能的多載方法來處理所有可能使用的型別,是不切實際的做法。 .NET 實作可透過參數陣列功能提供這類方法的支援。 在簽章中採用參數陣列的方法,可以使用任意數目的參數來提供。 參數會放入陣列中。 陣列元素的型別會決定可傳至函式的參數型別。 如果您將具有 System.Object 的參數陣列定義為元素型別,則用戶端程式碼可以傳遞任何型別的值。

在 F# 中,參數陣列只能定義於方法中。 您無法將其用於獨立函式或定義於模組中的函式。

您可以使用 ParamArray 屬性來定義參數陣列。 ParamArray 屬性只能套用至最後一個參數。

下列程式碼說明如何呼叫採用參數陣列的 .NET 方法,以及在 F# 中定義有方法採用參數陣列的型別。

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

在專案中執行時,先前程式碼的輸出如下所示:

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

另請參閱