模块
在 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
的顶级模块,MyModule1
和 MyModule2
在该顶级模块中嵌套。
// 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# 编译器仅在项目或编译命令行中只有一个文件,并且你正在创建应用程序时,才会隐式确定模块名称。
辅助功能修饰符可以是 public
、private
、internal
中的其中一个。 有关详细信息,请参阅访问控制。 默认值为 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 IsPeeled = false with get, set
member val Orientation = orientation with get, set
member val Sides: PeelState list = [ Unpeeled; Unpeeled; Unpeeled; Unpeeled] with get, set
member self.Peel() = BananaHelpers.peel self // Note the dependency on the BananaHelpers module.
member self.SqueezeJuiceOut() = raise (DontSqueezeTheBananaException self) // This member depends on the exception above.
module BananaHelpers =
let peel (b: 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)
match b.Orientation with
| Up -> b |> flip |> peelSides
| Down -> b |> peelSides
请注意,异常 DontSqueezeTheBananaException
和类 Banana
相互引用。 此外,模块 BananaHelpers
和类 Banana
也相互引用。 如果从 RecursiveModule
模块中移除 rec
关键字,则无法在 F# 中表示此引用。
在 F# 4.1 的命名空间中也可实现此功能。