代码格式设置准则 (F#)

本主题概述了 F# 语言的代码缩进准则。 由于 F# 语言对于换行符和缩进非常敏感,因此,正确地设置代码格式就不仅仅是可读性问题、美观问题或编码标准化问题了。 您必须正确地设置代码格式才能正确编译代码。

一般缩进规则

在需要缩进时,您必须使用空格,而不是制表符。 至少需要一个空格。 组织可以创建编码标准来指定要用于缩进的空格数;通常在进行缩进的每个级别使用三个或四个缩进空格。 可以更改**“选项”对话框(可从“工具”菜单中访问)中的选项,根据您组织的缩进标准对 Visual Studio 进行配置。 在 “文本编辑器”节点中,展开“F#”,然后单击“选项卡”**。有关可用选项的描述,请参见选项,文本编辑器,所有语言,选项卡

通常,当编译器分析代码时,它将保留一个用于指示当前嵌套级别的内部堆栈。 当代码缩进时,将会创建一个新嵌套级别,或者说会将一个新嵌套级别推入此内部堆栈。 构造结束时,该级别即会弹出。 缩进是一种指示级别结束并弹出内部堆栈的方式,但某些标记,例如 end 关键字或者右大括号或右括号,也会导致级别弹出。

多行构造(例如类型定义、函数定义、try...with 构造和循环构造)中的代码必须相对于构造的开始行加以缩进。 第一个缩进的行将为同一构造中后面的代码确定列位置。 缩进级别称为“上下文”。 列位置将为同一上下文中后面的代码行设置一个称为“越位线”的最小列。 当遇到缩进位置小于此规定列位置的代码行时,编译器将假定上下文已结束,并且您目前在比前面上下文高一个级别的位置编码。 术语“越位”用于描述代码行由于缩进距离不够而触发构造结束这样一种情况。 换言之,越位线左侧的代码处于越位位置。 在正确缩进的代码中,可以利用越位线来指示构造结束。 如果不正确地设置了缩进,则越位情况可能会导致编译器发出警告,或可能导致代码的解释不正确。

越位线按如下方式确定。

  • 与 let 关联的 = 标记会在 = 符号后面第一个标记的列位置处引入越位线。

  • 在 if...then...else 表达式中,then 关键字或 else 关键字后面第一个标记的列位置会引入越位线。

  • 在 try...with 表达式中,try 后面的第一个标记会引入越位线。

  • 在 match 表达式中,with 后面的第一个标记和每个 -> 后面的第一个标记会引入越位线。

  • 类型扩展中 with 后面的第一个标记会引入越位线。

  • 左大括号或左括号后面的第一个标记或者 begin 关键字后面的第一个标记会引入越位线。

  • 关键字 let、if 和 module 中的第一个字符会引入越位线。

下面的代码示例阐释了缩进规则。 此处的 print 语句依赖于缩进将自身与适当的上下文相关联。 缩进每次移位时,上下文都会弹出并返回到前一个上下文。 因此在每一个迭代的末尾都会打印一个空格;而“Done!”只会打印一次,原因是越位缩进已确定它不是循环的一部分。 输出的字符串“Top-level context”不是该函数的一部分。 因此,会在静态初始化过程中首先打印该字符串,然后再调用函数。

let printList list1 =
    for elem in list1 do
        if elem > 0 then
            printf "%d" elem
        elif elem = 0 then
            printf "Zero"
        else
            printf "(Negative number)"
        printf " "
    printfn "Done!"
printfn "Top-level context."
printList [-1;0;1;2;3]

输出如下所示。

Top-level context

(Negative number) Zero 1 2 3 Done!

将较长的行换行时,行的延续部分的缩进距离必须比封闭构造的缩进距离长。 例如,函数参数的缩进距离必须比函数名称第一个字符的缩进距离长,如下面的代码所示。

let myFunction1 a b = a + b
let myFunction2(a, b) = a + b
let someFunction param1 param2 =
    let result = myFunction1 param1
                     param2
    result * 100
let someOtherFunction param1 param2 =
    let result = myFunction2(param1,
                     param2)
    result * 100

如下一节中所述,这些规则存在例外情况。

模块中的缩进

局部模块中的代码必须相对于模块缩进,但顶级模块中的代码不必缩进。 命名空间元素不必缩进。

下面的代码示例阐释了这一点。

// Program1.fs
// A is a top-level module.
module A

let function1 a b = a - b * b
// Program2.fs
// A1 and A2 are local modules.
module A1 =
    let function1 a b = a*a + b*b

module A2 =
    let function2 a b = a*a - b*b

有关更多信息,请参见模块 (F#)

基本缩进规则的例外情况

如上一节中所述,一般规则是:多行构造中的代码必须相对于构造第一行的缩进加以缩进,并且,第一个越位行出现时即确定构造结束。 上下文何时结束的规则的一种例外情况是:由多个部分构成的某些构造,例如 try...with 表达式、if...then...else 表达式,以及使用 and 语法来声明相互递归函数或类型的情况。 在这些构造中,将后面的部分(例如 if...then...else 表达式中的 then 和 else)缩进到与表达式开头的标记相同的级别,并不指示上下文结束,而是表示同一上下文中的下一部分。 因此,可以按下面代码示例中的方式编写 if...then...else 表达式。

let abs1 x =
    if (x >= 0)
    then
        x
    else
        -x

越位规则的例外情况仅适用于 then 和 else 关键字。 因此,尽管进一步缩进 then 和 else 不是错误,但如果未能在 then 块中缩进代码行,则会生成警告。 下面的代码行中阐释了这一点。

// The following code does not produce a warning.
let abs2 x =
    if (x >= 0)
        then
        x
        else
        -x
// The following code is not indented properly and produces a warning.
let abs3 x =
    if (x >= 0)
    then
    x
    else
    -x

对于 else 块中的代码,将应用另外一条特殊规则。 只有 then 块中的代码才会出现前一示例中的警告,else 块中的代码绝不会这样。 这样,您将能够在函数的开头编写检查各种情况的代码,而不用强制函数中可能位于 else 块中的其余代码进行缩进。 因此,您可以编写以下代码而不会生成警告。

let abs4 x =
    if (x >= 0) then x else
    -x

对于上下文在某行的缩进距离与上一行不相等时结束的规则,另一种例外情况针对的是中辍运算符,例如 + 和 |>。 以中缀运算符开头的行的开始位置允许比正常位置靠前 (1 + oplength) 个列,而不会触发上下文结束,其中 oplength 是组成运算符的字符数。 这将导致运算符后面的第一个标记与前一行对齐。

例如,在下面的代码中,+ 符号允许比前一行少缩进两列。

let function1 arg1 arg2 arg3 arg4 =
    arg1 + arg2
  + arg3 + arg4

尽管缩进通常会随嵌套级别的提高而增加,但是,对于某些构造,编译器允许您将缩进重置为较低的列位置。

允许重置列位置的构造如下所示:

  • 匿名函数的主体。 在以下代码中,print 表达式的起始列位置远离 fun 关键字的左侧。 但是,行的起始列位置不能位于上一个缩进级别开头的左侧(即 List 中 L 的左侧)。

    let printListWithOffset a list1 =
        List.iter (fun elem ->
            printfn "%d" (a + elem)) list1
    
  • 包含在括号中的构造,或者包含在 if...then...else 表达式的 then 或 else 块内 begin 和 end 中的构造,条件是缩进不小于 if 关键字的列位置。 正因为有此例外,才会有在 then 或 else 后面一行的结束处使用左括号或 begin 的编码样式。

  • 由 begin...end、{...}、class...end 或 interface...end 分隔的模块、类、接口和结构的主体。 有了这一例外,类型定义的开头关键字才可以与类型名称位于同一行中,而无需强行要求整个主体比开头关键字多缩进一些。

    type IMyInterface = interface
       abstract Function1: int -> int
    end
    

请参见

其他资源

F# 语言参考