模块

在 F# 上下文中, 模块 是 F# 代码的分组,例如 F# 程序中的值、类型和函数值。 将模块中的代码分组有助于将相关代码保存在一起,并帮助避免程序中的名称冲突。

语法

// Top-level module declaration.
module [accessibility-modifier] [qualified-namespace.]module-name
declarations
// Local module declaration.
module [accessibility-modifier] module-name =
    declarations

注解

F# 模块是 F# 代码构造的分组,例如绑定中的 do 类型、值、函数值和代码。 它作为仅具有静态成员的公共语言运行时 (CLR) 类实现。 有两种类型的模块声明,具体取决于整个文件是否包含在模块中:顶级模块声明和本地模块声明。 顶级模块声明包括模块中的整个文件。 顶级模块声明只能显示为文件中的第一个声明。

在顶级模块声明的语法中,可选的 限定命名空间 是包含该模块的嵌套命名空间名称序列。 不需要之前声明限定的命名空间。

无需在顶级模块中缩进声明。 必须缩进本地模块中的所有声明。 在本地模块声明中,只有在该模块声明下缩进的声明是模块的一部分。

如果代码文件不以顶级模块声明或命名空间声明开头,则文件的全部内容(包括任何本地模块)将成为隐式创建的顶级模块的一部分,该模块的名称与文件名称相同,没有扩展名,第一个字母转换为大写。 例如,请考虑以下文件。

// In the file program.fs.
let x = 40

此文件将像以这种方式编写一样进行编译:

module Program
let x = 40

如果文件中有多个模块,则必须为每个模块使用本地模块声明。 如果声明了封闭命名空间,则这些模块是封闭命名空间的一部分。 如果未声明封闭命名空间,模块将成为隐式创建的顶级模块的一部分。 下面的代码示例演示包含多个模块的代码文件。 编译器隐式创建一个名为Multiplemodules的顶级模块,并MyModule1MyModule2嵌套在该顶级模块中。

// In the file multiplemodules.fs.
// MyModule1
module MyModule1 =
    // Indent all program elements within modules that are declared with an equal sign.
    let module1Value = 100

    let module1Function x =
        x + 10

// MyModule2
module MyModule2 =

    let module2Value = 121

    // Use a qualified name to access the function.
    // from MyModule1.
    let module2Function x =
        x * (MyModule1.module1Function module2Value)

如果在项目或单个编译中有多个文件,或者要生成库,则必须在文件顶部包含命名空间声明或模块声明。 F# 编译器仅在项目或编译命令行中只有一个文件时隐式确定模块名称,并且要创建应用程序。

辅助功能修饰符可以是下列项之一:publicprivateinternal、 。 有关详细信息,请参阅 访问控制。 默认值为 public。

在模块中引用代码

引用另一个模块中的函数、类型和值时,必须使用限定的名称或打开模块。 如果使用限定名称,则必须指定所需的程序元素的命名空间、模块和标识符。 使用点(.)分隔限定路径的每个部分,如下所示。

Namespace1.Namespace2.ModuleName.Identifier

可以打开模块或一个或多个命名空间来简化代码。 有关打开命名空间和模块的详细信息,请参阅导入声明:关键字open

下面的代码示例显示了一个顶级模块,该模块包含文件末尾的所有代码。

module Arithmetic

let add x y =
    x + y

let sub x y =
    x - y

若要从同一项目中的另一个文件中使用此代码,请使用限定的名称,或者在使用函数之前打开模块,如以下示例所示。

// Fully qualify the function name.
let result1 = Arithmetic.add 5 9
// Open the module.
open Arithmetic
let result2 = add 5 9

嵌套模块

模块可以嵌套。 内部模块必须缩进到外部模块声明,以指示它们是内部模块,而不是新模块。 例如,比较以下两个示例。 模块 Z 是以下代码中的内部模块。

module Y =
    let x = 1

    module Z =
        let z = 5

但模块 Z 是以下代码中模块 Y 的同级模块。

module Y =
    let x = 1

module Z =
    let z = 5

模块 Z 也是以下代码中的同级模块,因为它与模块 Y中的其他声明不缩进。

module Y =
        let x = 1

    module Z =
        let z = 5

最后,如果外部模块没有声明,并且紧随另一个模块声明,则新模块声明假定为内部模块,但如果第二个模块定义不缩进到第一个模块定义之外,编译器将发出警告。

// This code produces a warning, but treats Z as a inner module.
module Y =
module Z =
    let z = 5

若要消除警告,请缩进内部模块。

module Y =
    module Z =
        let z = 5

如果希望文件中的所有代码都位于单个外部模块中,并且需要内部模块,则外部模块不需要等号,并且声明(包括任何内部模块声明)无需缩进外部模块。 内部模块声明内的声明必须缩进。 以下代码显示了此情况。

// The top-level module declaration can be omitted if the file is named
// TopLevel.fs or topLevel.fs, and the file is the only file in an
// application.
module TopLevel

let topLevelX = 5

module Inner1 =
    let inner1X = 1
module Inner2 =
    let inner2X = 5

递归模块

F# 4.1 引入了允许所有包含代码相互递归的模块的概念。 这是通过 module rec. module rec使用可以减轻在类型与模块之间无法编写相互引用代码的一些痛苦。 下面是一个示例:

module rec RecursiveModule =
    type Orientation = Up | Down
    type PeelState = Peeled | Unpeeled

    // This exception depends on the type below.
    exception DontSqueezeTheBananaException of Banana

    type Banana(orientation : Orientation) =
        member val Orientation = orientation with get, set
        member val Sides: PeelState list = [ Unpeeled; Unpeeled; Unpeeled; Unpeeled ] with get, set

        member self.IsPeeled =
            self.Sides |> List.forall ((=) Peeled)

        member self.Peel() =
            BananaHelpers.peel self
            |> fun peeledSides -> self.Sides <- peeledSides

        member self.SqueezeJuiceOut() =
            raise (DontSqueezeTheBananaException self)

    module BananaHelpers =
        let peel (banana: Banana) =
            let flip (banana: Banana) =
                match banana.Orientation with
                | Up ->
                    banana.Orientation <- Down
                    banana
                | Down -> banana

            let peelSides (banana: Banana) =
                banana.Sides
                |> List.map (function
                             | Unpeeled -> Peeled
                             | Peeled -> Peeled)

            banana |> flip |> peelSides

请注意,异常 DontSqueezeTheBananaException 和类 Banana 都相互引用。 此外,模块 BananaHelpers 和类 Banana 也相互引用。 如果从RecursiveModule模块中删除了rec关键字,则无法在 F# 中表达。

在 F# 4.1 的命名空间 中也可以实现此功能。

另请参阅