代码格式设置准则 (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