在 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 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 的命名空间 中也可以实现此功能。