形参和实参 (F#)

本主题介绍用于定义形参并将实参传递给函数、方法和属性的语言支持, 其中包括有关如何通过引用传递以及如何定义和使用可采用可变数目的参数的方法的信息。

参数和变量

术语“形参”用于描述应提供的值的名称。 术语“实参”用于表示为每个形参提供的值。

可以使用元组形式或扩充形式指定形参,也可以使用这两种形式的某些组合来指定形参。 可以使用显式形参名来传递实参。 可以将方法的形参指定为可选项并给定一个默认值。

形参模式

一般来说,提供给函数和方法的形参都是用空格分隔的模式。 从原则上来说,这意味着可以在函数或成员的形参列表中使用match 表达式 (F#)中描述的任何模式。

方法通常会使用传递实参的元组形式。 从其他 .NET 语言的角度来看,这将获得更加清晰的结果,因为元组形式与在 .NET 方法中传递实参的方式相匹配。

扩充形式常用于通过使用 let 绑定创建的函数。

下面的伪代码显示了元组和扩充参数的示例。

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

当一些实参采用元组形式,而一些实参不采用元组形式时,可以使用组合形式。

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#)

可选参数

可以在形参名称前使用一个问号来指定方法的可选形参。 由于可选形参将解释为 F# 选项类型,因此您可以通过将 match 表达式与 Some 和 None 结合使用,按照查询选项类型的一般方式来查询可选形参。 可选形参仅适用于成员,而不适用于使用 let 绑定创建的函数。

也可以使用函数 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)

输出如下所示。

Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false
Baud Rate: 300 Duplex: Half Parity: true

通过引用传递

F# 支持 byref 关键字,此关键字指定通过引用传递形参。 这意味着,在执行函数后将保留对值所做的所有更改。 为 byref 形参提供的值必须是可变的。 或者,可以传递适当类型的引用单元格。

在 .NET 语言中,通过引用传递已演变为一种从一个函数返回多个值的方法。 在 F# 中,可以为此目的返回一个元组,也可以将可变的引用单元格用作形参。 byref 形参主要是为与 .NET 库进行交互操作而提供的。

下面的示例阐释了 byref 关键字的用法。 请注意,在将引用单元格用作形参时,必须创建一个引用单元格作为命名值并将其用作形参,而不只是添加 ref 运算符,如以下代码中对 Increment 的第一个调用中所示。 由于创建引用单元格的同时会创建基础值的副本,因此第一个调用只会递增临时值。

type Incrementor(z) =
    member this.Increment(i : int byref) =
       i <- i + z

let incrementor = new Incrementor(1)
let mutable x = 10
// Not recommended: Does not actually increment the variable.
incrementor.Increment(ref x)
// Prints 10.
printfn "%d" x  

let mutable y = 10
incrementor.Increment(&y)
// Prints 11.
printfn "%d" y 

let refInt = ref 10
incrementor.Increment(refInt)
// Prints 11.
printfn "%d" !refInt  

可以将元组用作返回值,以在 .NET 库方法中存储任何 out 形参。 或者,可以将 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 平台通过形参数组功能为此类方法提供支持。 可以为在其签名中采用形参数组的方法提供任意数量的形参。 这些形参放置于一个数组中。 数组元素的类型将确定可传递给函数的形参类型。 如果将带有 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

请参见

其他资源

成员 (F#)