另请参阅 编码约定 组件设计指南 ,其中还包括命名约定。

自动代码格式设置

Fantomas 代码格式化程序 是用于实现自动代码格式设置的 F# 社区标准工具。 默认设置对应于此样式指南。

我们强烈建议使用此代码格式化程序。 在 F# 团队中,代码格式设置规范应根据签入团队存储库的代码格式化程序的约定设置文件达成一致和编纂。

格式设置的一般规则

默认情况下,F# 使用有效空格,并且对空格敏感。 下面的指南旨在针对如何应对这可能带来的一些挑战提供指导。

使用空格而不是制表符

如果需要缩进,则必须使用空格,而不是制表符。 F# 代码不使用制表符,如果在字符串字面量或注释之外遇到制表符字符,则编译器将会给出错误。

使用一致的缩进

缩进时,至少需要一个空格。 组织可以创建编码标准来指定用于缩进的空格数;典型的做法是在缩进出现的各个级别使用两个、三个或四个空格进行缩进。

我们建议每个缩进使用四个空格。

也就是说,程序的缩进是一个主观问题。 可以有变化,但应遵循的第一条规则是缩进的一致性。 选择一种普遍接受的缩进样式,并在整个代码库中系统地使用它。

避免设置为对名称长度敏感的格式

尽量避免对命名敏感的缩进和对齐:

// ✔️ OK
let myLongValueName =
    someExpression
    |> anotherExpression
// ❌ Not OK
let myLongValueName = someExpression
                      |> anotherExpression
// ✔️ OK
let myOtherVeryLongValueName =
    match
        someVeryLongExpressionWithManyParameters
            parameter1
            parameter2
            parameter3
    | Some _ -> ()
    | ...
// ❌ Not OK
let myOtherVeryLongValueName =
    match someVeryLongExpressionWithManyParameters parameter1
                                                   parameter2
                                                   parameter3 with
    | Some _ -> ()
    | ...
// ❌ Still Not OK
let myOtherVeryLongValueName =
    match someVeryLongExpressionWithManyParameters
              parameter1
              parameter2
              parameter3 with
    | Some _ -> ()
    | ...

避免此格式设置的主要原因是:

  • 重要代码会移到最右边
  • 留给实际代码的宽度较少
  • 重命名可能会破坏对齐
  • 避免多余的空格

    避免在 F# 代码中使用多余的空格(此样式指南中所述的情况除外)。

    // ✔️ OK
    spam (ham 1)
    // ❌ Not OK
    spam ( ham 1 )
    

    设置注释格式

    优先使用多个双斜杠注释而不是块注释。

    // Prefer this style of comments when you want
    // to express written ideas on multiple lines.
        Block comments can be used, but use sparingly.
        They are useful when eliding code sections.
    

    注释的第一个字母应该大写,并且是格式标准的短语或句子。

    // ✔️ A good comment.
    let f x = x + 1 // Increment by one.
    // ❌ two poor comments
    let f x = x + 1 // plus one
    

    有关设置 XML 文档注释格式的信息,请参阅下面的“格式设置声明”。

    设置表达式格式

    本部分介绍如何设置不同类型的表达式的格式。

    设置字符串表达式格式

    字符串字面量和内插字符串都可以只保留在一行中,无论该行有多长。

    let serviceStorageConnection =
        $"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
    

    不鼓励使用多行内插表达式, 而是将表达式结果绑定到一个值,并在内插字符串中使用该值。

    设置元组表达式格式

    元组实例化应该用圆括号括起来,其中的分隔逗号后面应该跟一个空格,例如:(1, 2)(x, y, z)

    // ✔️ OK
    let pair = (1, 2)
    let triples = [ (1, 2, 3); (11, 12, 13) ]
    

    在元组的模式匹配中,通常可以省略括号:

    // ✔️ OK
    let (x, y) = z
    let x, y = z
    // ✔️ OK
    match x, y with
    | 1, _ -> 0
    | x, 1 -> 0
    | x, y -> 1
    

    如果元组是函数的返回值,通常也可以省略括号:

    // ✔️ OK
    let update model msg =
        match msg with
        | 1 -> model + 1, []
        | _ -> model, [ msg ]
    

    总之,最好使用带圆括号的元组实例化,但是当使用模式匹配或返回值的元组时,最好避免使用括号。

    设置应用程序表达式格式

    设置函数或方法应用程序的格式时,如果行宽允许,则在同一行上提供参数:

    // ✔️ OK
    someFunction1 x.IngredientName x.Quantity
    

    除非参数需要括号,否则请省略括号:

    // ✔️ OK
    someFunction1 x.IngredientName
    // ❌ Not preferred - parentheses should be omitted unless required
    someFunction1 (x.IngredientName)
    // ✔️ OK - parentheses are required
    someFunction1 (convertVolumeToLiter x)
    

    使用多个扩充参数进行调用时不要省略空格:

    // ✔️ OK
    someFunction1 (convertVolumeToLiter x) (convertVolumeUSPint x)
    someFunction2 (convertVolumeToLiter y) y
    someFunction3 z (convertVolumeUSPint z)
    // ❌ Not preferred - spaces should not be omitted between arguments
    someFunction1(convertVolumeToLiter x)(convertVolumeUSPint x)
    someFunction2(convertVolumeToLiter y) y
    someFunction3 z(convertVolumeUSPint z)
    

    在默认格式设置约定中,将小写函数应用于元组或带圆括号的参数(即使使用单个参数)时会添加一个空格:

    // ✔️ OK
    someFunction2 ()
    // ✔️ OK
    someFunction3 (x.Quantity1 + x.Quantity2)
    // ❌ Not OK, formatting tools will add the extra space by default
    someFunction2()
    // ❌ Not OK, formatting tools will add the extra space by default
    someFunction3(x.IngredientName, x.Quantity)
    

    在默认格式设置约定中,将大写方法应用于元组参数时不添加空格。 这是因为它们通常与 fluent programming 一起使用:

    // ✔️ OK - Methods accepting parenthesize arguments are applied without a space
    SomeClass.Invoke()
    // ✔️ OK - Methods accepting tuples are applied without a space
    String.Format(x.IngredientName, x.Quantity)
    // ❌ Not OK, formatting tools will remove the extra space by default
    SomeClass.Invoke ()
    // ❌ Not OK, formatting tools will remove the extra space by default
    String.Format (x.IngredientName, x.Quantity)
    

    你可能需要在新行上将参数传递给函数,出于可读性考虑或因为参数列表或参数名称太长。 在这种情况下,缩进一级:

    // ✔️ OK
    someFunction2
        x.IngredientName x.Quantity
    // ✔️ OK
    someFunction3
        x.IngredientName1 x.Quantity2
        x.IngredientName2 x.Quantity2
    // ✔️ OK
    someFunction4
        x.IngredientName1
        x.Quantity2
        x.IngredientName2
        x.Quantity2
    // ✔️ OK
    someFunction5
        (convertVolumeToLiter x)
        (convertVolumeUSPint x)
        (convertVolumeImperialPint x)
    

    当函数采用单个多行元组参数时,将每个参数放在一个新行中:

    // ✔️ OK
    someTupledFunction (
        478815516,
        "A very long string making all of this multi-line",
        1515,
        false
    // OK, but formatting tools will reformat to the above
    someTupledFunction
        (478815516,
         "A very long string making all of this multi-line",
         1515,
         false)
    

    如果参数表达式很短,请用空格分隔参数并将其放在一行中。

    // ✔️ OK
    let person = new Person(a1, a2)
    // ✔️ OK
    let myRegexMatch = Regex.Match(input, regex)
    // ✔️ OK
    let untypedRes = checker.ParseFile(file, source, opts)
    

    如果参数表达式很长,请使用换行符并缩进一级,而不是缩进到左括号。

    // ✔️ OK
    let person =
        new Person(
            argument1,
            argument2
    // ✔️ OK
    let myRegexMatch =
        Regex.Match(
            "my longer input string with some interesting content in it",
            "myRegexPattern"
    // ✔️ OK
    let untypedRes =
        checker.ParseFile(
            fileName,
            sourceText,
            parsingOptionsWithDefines
    // ❌ Not OK, formatting tools will reformat to the above
    let person =
        new Person(argument1,
                   argument2)
    // ❌ Not OK, formatting tools will reformat to the above
    let untypedRes =
        checker.ParseFile(fileName,
                          sourceText,
                          parsingOptionsWithDefines)
    

    即使只有一个多行参数(包括多行字符串),同样的规则也适用:

    // ✔️ OK
    let poemBuilder = StringBuilder()
    poemBuilder.AppendLine(
    The last train is nearly due
    The Underground is closing soon
    And in the dark, deserted station
    Restless in anticipation
    A man waits in the shadows
    Option.traverse(
        create
        >> Result.setError [ invalidHeader "Content-Checksum" ]
    

    设置管道表达式格式

    使用多行时,管道 |> 运算符应位于它们所操作的表达式下方。

    // ✔️ OK
    let methods2 =
        System.AppDomain.CurrentDomain.GetAssemblies()
        |> List.ofArray
        |> List.map (fun assm -> assm.GetTypes())
        |> Array.concat
        |> List.ofArray
        |> List.map (fun t -> t.GetMethods())
        |> Array.concat
    // ❌ Not OK, add a line break after "=" and put multi-line pipelines on multiple lines.
    let methods2 = System.AppDomain.CurrentDomain.GetAssemblies()
                |> List.ofArray
                |> List.map (fun assm -> assm.GetTypes())
                |> Array.concat
                |> List.ofArray
                |> List.map (fun t -> t.GetMethods())
                |> Array.concat
    // ❌ Not OK either
    let methods2 = System.AppDomain.CurrentDomain.GetAssemblies()
                   |> List.ofArray
                   |> List.map (fun assm -> assm.GetTypes())
                   |> Array.concat
                   |> List.ofArray
                   |> List.map (fun t -> t.GetMethods())
                   |> Array.concat
    

    设置 Lambda 表达式格式

    当 Lambda 表达式用作多行表达式中的参数并且后跟其他参数时,将 Lambda 表达式的主体放在新行中,缩进一级:

    // ✔️ OK
    let printListWithOffset a list1 =
        List.iter
            (fun elem ->
                 printfn $"A very long line to format the value: %d{a + elem}")
            list1
    

    如果 Lambda 参数是函数应用程序中的最后一个参数,则将所有参数放在同一行,直到出现箭头为止。

    // ✔️ OK
    Target.create "Build" (fun ctx ->
        // code
        // here
    // ✔️ OK
    let printListWithOffsetPiped a list1 =
        list1
        |> List.map (fun x -> x + 1)
        |> List.iter (fun elem ->
            printfn $"A very long line to format the value: %d{a + elem}")
    

    以类似的方式处理 match Lambda。

    // ✔️ OK
    functionName arg1 arg2 arg3 (function
        | Choice1of2 x -> 1
        | Choice2of2 y -> 2)
    

    如果 Lambda 之前有许多前导或多行参数,所有参数都缩进一级。

    // ✔️ OK
    functionName
        (fun arg4 ->
            bodyExpr)
    // ✔️ OK
    functionName
        (function
         | Choice1of2 x -> 1
         | Choice2of2 y -> 2)
    

    如果 Lambda 表达式的主体长度为多行,则应考虑将其重构为局部范围的函数。

    当管道包含 Lambda 表达式时,每个 Lambda 表达式通常都是管道的每个阶段的最后一个参数:

    // ✔️ OK, with 4 spaces indentation
    let printListWithOffsetPiped list1 =
        list1
        |> List.map (fun elem -> elem + 1)
        |> List.iter (fun elem ->
            // one indent starting from the pipe
            printfn $"A very long line to format the value: %d{elem}")
    // ✔️ OK, with 2 spaces indentation
    let printListWithOffsetPiped list1 =
      list1
      |> List.map (fun elem -> elem + 1)
      |> List.iter (fun elem ->
        // one indent starting from the pipe
        printfn $"A very long line to format the value: %d{elem}")
    

    如果 lambda 的参数不放在单行中,或者本身是多行,则将其放在下一行,并缩进一级。

    // ✔️ OK
        (aVeryLongParameterName: AnEquallyLongTypeName)
        (anotherVeryLongParameterName: AnotherLongTypeName)
        (yetAnotherLongParameterName: LongTypeNameAsWell)
        (youGetTheIdeaByNow: WithLongTypeNameIncluded) ->
        // code starts here
    // ❌ Not OK, code formatters will reformat to the above to respect the maximum line length.
    fun (aVeryLongParameterName: AnEquallyLongTypeName) (anotherVeryLongParameterName: AnotherLongTypeName) (yetAnotherLongParameterName: LongTypeNameAsWell) (youGetTheIdeaByNow: WithLongTypeNameIncluded) ->
    // ✔️ OK
    let useAddEntry () =
            (input:
                {| name: string
                   amount: Amount
                   isIncome: bool
                   created: string |}) ->
             // foo
             bar ()
    // ❌ Not OK, code formatters will reformat to the above to avoid reliance on whitespace alignment that is contingent to length of an identifier.
    let useAddEntry () =
        fun (input: {| name: string
                       amount: Amount
                       isIncome: bool
                       created: string |}) ->
            // foo
            bar ()
    

    设置算术和二进制表达式的格式

    在二进制算术表达式周围始终使用空格:

    // ✔️ OK
    let subtractThenAdd x = x - 1 + 3
    

    与某些格式设置选项结合使用时,如果未能将二元 - 运算符括起来,则可能会导致将其解释为一元 -。 一元 - 运算符后应始终紧跟它们要取反的值:

    // ✔️ OK
    let negate x = -x
    // ❌ Not OK
    let negateBad x = - x
    

    - 运算符之后添加空格字符可能会导致其他人混淆。

    用空格分隔二元运算符。 中缀表达式可以排列在同一列:

    // ✔️ OK
    let function1 () =
        acc +
        (someFunction
             x.IngredientName x.Quantity)
    // ✔️ OK
    let function1 arg1 arg2 arg3 arg4 =
        arg1 + arg2 +
        arg3 + arg4
    

    此规则也适用于类型和常量批注中的度量单位:

    // ✔️ OK
    type Test =
        { WorkHoursPerWeek: uint<hr / (staff weeks)> }
        static member create = { WorkHoursPerWeek = 40u<hr / (staff weeks)> }
    // ❌ Not OK
    type Test =
        { WorkHoursPerWeek: uint<hr/(staff weeks)> }
        static member create = { WorkHoursPerWeek = 40u<hr/(staff weeks)> }
    

    应使用以下在 F# 标准库中定义的运算符,而不是定义等效项。 建议使用这些运算符,因为它们会使代码更具可读性和惯用性。 以下列表总结了推荐的 F# 运算符。

    // ✔️ OK
    x |> f // Forward pipeline
    f >> g // Forward composition
    x |> ignore // Discard away a value
    x + y // Overloaded addition (including string concatenation)
    x - y // Overloaded subtraction
    x * y // Overloaded multiplication
    x / y // Overloaded division
    x % y // Overloaded modulus
    x && y // Lazy/short-cut "and"
    x || y // Lazy/short-cut "or"
    x <<< y // Bitwise left shift
    x >>> y // Bitwise right shift
    x ||| y // Bitwise or, also for working with “flags” enumeration
    x &&& y // Bitwise and, also for working with “flags” enumeration
    x ^^^ y // Bitwise xor, also for working with “flags” enumeration
    

    设置范围运算符表达式的格式

    仅当所有表达式均为非原子时,才在 .. 周围添加空格。 整数和单字标识符被认为是原子的。

    // ✔️ OK
    let a = [ 2..7 ] // integers
    let b = [ one..two ] // identifiers
    let c = [ ..9 ] // also when there is only one expression
    let d = [ 0.7 .. 9.2 ] // doubles
    let e = [ 2L .. number / 2L ] // complex expression
    let f = [| A.B .. C.D |] // identifiers with dots
    let g = [ .. (39 - 3) ] // complex expression
    let h = [| 1 .. MyModule.SomeConst |] // not all expressions are atomic
    for x in 1..2 do
        printfn " x = %d" x
    let s = seq { 0..10..100 }
    // ❌ Not OK
    let a = [ 2 .. 7 ]
    let b = [ one .. two ]
    

    这些规则也适用于切片:

    // ✔️ OK
    arr[0..10]
    list[..^1]
    

    设置 if 表达式格式

    条件句的缩进取决于构成它们的表达式的大小和复杂性。 在以下情况下,将它们写在一行:

  • conde1e2 很短。
  • e1e2 本身不是 if/then/else 表达式。
  • // ✔️ OK
    if cond then e1 else e2
    

    如果不存在 else 表达式,建议不要在一行中编写整个表达式。 这是为了区分命令性代码与功能性代码。

    // ✔️ OK
    if a then
    // ❌ Not OK, code formatters will reformat to the above by default
    if a then ()
    

    如果任何一个表达式都是多行的,则每个条件分支都应该是多行的。

    // ✔️ OK
    if cond then
        let e1 = something()
    // ❌ Not OK
    if cond then
        let e1 = something()
    else e2
    

    带有 elifelse 的多个条件句在遵循一行 if/then/else 表达式的规则时,其缩进范围与 if 相同。

    // ✔️ OK
    if cond1 then e1
    elif cond2 then e2
    elif cond3 then e3
    else e4
    

    如果任何条件或表达式是多行的,则整个 if/then/else 表达式是多行的:

    // ✔️ OK
    if cond1 then
        let e1 = something()
    elif cond2 then
    elif cond3 then
    // ❌ Not OK
    if cond1 then
        let e1 = something()
    elif cond2 then e2
    elif cond3 then e3
    else e4
    

    如果条件跨多行或超过一行的默认容许长度,则条件表达式应使用一个字符的缩进和一个新行。 封装较长的条件表达式时,ifthen 关键字应该相符。

    // ✔️ OK, but better to refactor, see below
        complexExpression a b && env.IsDevelopment()
        || someFunctionToCall
            aVeryLongParameterNameOne
            aVeryLongParameterNameTwo
            aVeryLongParameterNameThree 
    // ✔️The same applies to nested `elif` or `else if` expressions
    if a then
        someLongFunctionCall
            argumentOne
            argumentTwo
            argumentThree
            argumentFour
    else if
        someOtherLongFunctionCall
            argumentOne
            argumentTwo
            argumentThree
            argumentFour
    

    但最好将长条件重构为 let 绑定或单独的函数:

    // ✔️ OK
    let performAction =
        complexExpression a b && env.IsDevelopment()
        || someFunctionToCall
            aVeryLongParameterNameOne
            aVeryLongParameterNameTwo
            aVeryLongParameterNameThree
    if performAction then
    

    设置联合用例表达式的格式

    应用可区分的联合用例遵循与函数和方法应用程序相同的规则。 也就是说,因为名称是大写的,代码格式化程序将删除元组前的空格:

    // ✔️ OK
    let opt = Some("A", 1)
    // OK, but code formatters will remove the space
    let opt = Some ("A", 1)
    

    像函数应用程序一样,拆分为多行的结构应使用缩进:

    // ✔️ OK
    let tree1 =
        BinaryNode(
            BinaryNode (BinaryValue 1, BinaryValue 2),
            BinaryNode (BinaryValue 3, BinaryValue 4)
    

    设置列表和数组表达式的格式

    撰写 x :: l,在 :: 运算符周围添加空格(:: 是中缀运算符,因此会被空格包围)。

    在单行中声明的列表和数组应该在左方括号之后和右方括号之前添加空格:

    // ✔️ OK
    let xs = [ 1; 2; 3 ]
    // ✔️ OK
    let ys = [| 1; 2; 3; |]
    

    始终在两个不同的类似大括号的运算符之间使用至少一个空格。 例如,在 [{ 之间留一个空格。

    // ✔️ OK
    [ { Ingredient = "Green beans"; Quantity = 250 }
      { Ingredient = "Pine nuts"; Quantity = 250 }
      { Ingredient = "Feta cheese"; Quantity = 250 }
      { Ingredient = "Olive oil"; Quantity = 10 }
      { Ingredient = "Lemon"; Quantity = 1 } ]
    // ❌ Not OK
    [{ Ingredient = "Green beans"; Quantity = 250 }
     { Ingredient = "Pine nuts"; Quantity = 250 }
     { Ingredient = "Feta cheese"; Quantity = 250 }
     { Ingredient = "Olive oil"; Quantity = 10 }
     { Ingredient = "Lemon"; Quantity = 1 }]
    

    相同的指南适用于元组的列表或数组。

    拆分为多行的列表和数组遵循与记录类似的规则:

    // ✔️ OK
    let pascalsTriangle =
        [| [| 1 |]
           [| 1; 1 |]
           [| 1; 2; 1 |]
           [| 1; 3; 3; 1 |]
           [| 1; 4; 6; 4; 1 |]
           [| 1; 5; 10; 10; 5; 1 |]
           [| 1; 6; 15; 20; 15; 6; 1 |]
           [| 1; 7; 21; 35; 35; 21; 7; 1 |]
           [| 1; 8; 28; 56; 70; 56; 28; 8; 1 |] |]
    

    与记录一样,在左方括号和右方括号各自所在的行上声明它们将使移动代码和通过管道移入函数中更为容易:

    // ✔️ OK
    let pascalsTriangle =
            [| 1 |]
            [| 1; 1 |]
            [| 1; 2; 1 |]
            [| 1; 3; 3; 1 |]
            [| 1; 4; 6; 4; 1 |]
            [| 1; 5; 10; 10; 5; 1 |]
            [| 1; 6; 15; 20; 15; 6; 1 |]
            [| 1; 7; 21; 35; 35; 21; 7; 1 |]
            [| 1; 8; 28; 56; 70; 56; 28; 8; 1 |] 
    

    如果列表或数组表达式位于绑定的右侧,你可能更喜欢使用 Stroustrup 样式:

    // ✔️ OK
    let pascalsTriangle = [| 
       [| 1 |]
       [| 1; 1 |]
       [| 1; 2; 1 |]
       [| 1; 3; 3; 1 |]
       [| 1; 4; 6; 4; 1 |]
       [| 1; 5; 10; 10; 5; 1 |]
       [| 1; 6; 15; 20; 15; 6; 1 |]
       [| 1; 7; 21; 35; 35; 21; 7; 1 |]
       [| 1; 8; 28; 56; 70; 56; 28; 8; 1 |] 
    

    但是,如果列表或数组表达式不在绑定的右侧,例如当它位于另一个列表或数组的内部时,如果该内部表达式需要跨越多行,则括号应位于其所在的行中:

    // ✔️ OK - The outer list follows `Stroustrup` style, while the inner lists place their brackets on separate lines
    let fn a b = [ 
            someReallyLongValueThatWouldForceThisListToSpanMultipleLines
            someReallyLongValueThatWouldForceThisListToSpanMultipleLines 
    // ❌ Not okay
    let fn a b = [ [
        someReallyLongValueThatWouldForceThisListToSpanMultipleLines
        someReallyLongValueThatWouldForceThisListToSpanMultipleLines
    

    同样的规则也适用于数组/列表中的记录类型:

    // ✔️ OK - The outer list follows `Stroustrup` style, while the inner lists place their brackets on separate lines
    let fn a b = [ 
            Foo = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
            Bar = a
            Foo = b
            Bar = someReallyLongValueThatWouldForceThisListToSpanMultipleLines 
    // ❌ Not okay
    let fn a b = [ {
        Foo = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
        Bar = a
        Foo = b
        Bar = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
    

    当以编程方式生成数组和列表时,如果总是生成一个值,则最好使用 -> 而不是 do ... yield

    // ✔️ OK
    let squares = [ for x in 1..10 -> x * x ]
    // ❌ Not preferred, use "->" when a value is always generated
    let squares' = [ for x in 1..10 do yield x * x ]
    

    在可能有条件地生成数据或可能有连续表达式要计算的情况下,旧版 F# 需要指定 yield。 除非必须使用较旧的 F# 语言版本进行编译,否则最好省略这些 yield 关键字:

    // ✔️ OK
    let daysOfWeek includeWeekend =
            "Monday"
            "Tuesday"
            "Wednesday"
            "Thursday"
            "Friday"
            if includeWeekend then
                "Saturday"
                "Sunday"
    // ❌ Not preferred - omit yield instead
    let daysOfWeek' includeWeekend =
            yield "Monday"
            yield "Tuesday"
            yield "Wednesday"
            yield "Thursday"
            yield "Friday"
            if includeWeekend then
                yield "Saturday"
                yield "Sunday"
    

    在某些情况下,do...yield 可能有助于提高可读性。 这些情况虽然是主观的,但应予以考虑。

    设置记录表达式格式

    短记录可写在一行中:

    // ✔️ OK
    let point = { X = 1.0; Y = 0.0 }
    

    较长的记录应为标签使用新行:

    // ✔️ OK
    let rainbow =
        { Boss = "Jeffrey"
          Lackeys = ["Zippy"; "George"; "Bungle"] }
    

    多行括号格式设置样式

    对于跨越多行的记录,有三种常用的格式设置样式:CrampedAlignedStroustrupCramped 样式一直是 F# 代码的默认样式,因为它倾向于使用允许编译器轻松分析代码的样式。 AlignedStroustrup 样式都允许更轻松地重新排序成员,从而使代码更容易重构,缺点是某些情况可能需要稍微冗长的代码。

  • Cramped:历史标准,默认的 F# 记录格式。 左括号与第一个成员在同一行,右括号与最后一个成员在同一行。

    let rainbow = 
        { Boss1 = "Jeffrey"
          Boss2 = "Jeffrey"
          Boss3 = "Jeffrey"
          Lackeys = [ "Zippy"; "George"; "Bungle" ] }
    
  • Aligned:每个括号都有自己的行,在同一列上对齐。

    let rainbow =
            Boss1 = "Jeffrey"
            Boss2 = "Jeffrey"
            Boss3 = "Jeffrey"
            Lackeys = ["Zippy"; "George"; "Bungle"]
    
  • Stroustrup:左括号与绑定在同一行,右括号位于自己所在的行。

    let rainbow = {
        Boss1 = "Jeffrey"
        Boss2 = "Jeffrey"
        Boss3 = "Jeffrey"
        Lackeys = [ "Zippy"; "George"; "Bungle" ]
    

    相同的格式设置样式规则也适用于列表和数组元素。

    设置复制和更新记录表达式的格式

    复制和更新记录表达式仍然是记录,因此适用类似的指南。

    短表达式可放在一行中:

    // ✔️ OK
    let point2 = { point with X = 1; Y = 2 }
    

    较长的表达式应使用新的行,并根据上述约定之一设置格式:

    // ✔️ OK - Cramped
    let newState =
        { state with
            Foo =
                    { F1 = 0
                      F2 = "" } }
    // ✔️ OK - Aligned
    let newState = 
            state with
                Foo =
                            F1 = 0
                            F2 = ""
    // ✔️ OK - Stroustrup
    let newState = { 
        state with
            Foo =
                Some { 
                    F1 = 0
                    F2 = ""
    

    注意:如果对复制和更新表达式使用 Stroustrup 样式,则必须进一步缩进成员而不是复制的记录名称:

    // ✔️ OK
    let bilbo = {
        hobbit with 
            Name = "Bilbo"
            Age = 111
            Region = "The Shire" 
    // ❌ Not OK - Results in compiler error: "Possible incorrect indentation: this token is offside of context started at position"
    let bilbo = {
        hobbit with 
        Name = "Bilbo"
        Age = 111
        Region = "The Shire" 
    

    设置模式匹配格式

    对于没有缩进的 match 的每个子句,使用 |。 如果表达式很短,并且每个子表达式也很简单,则可以考虑使用单行。

    // ✔️ OK
    match l with
    | { him = x; her = "Posh" } :: tail -> x
    | _ :: tail -> findDavid tail
    | [] -> failwith "Couldn't find David"
    // ❌ Not OK, code formatters will reformat to the above by default
    match l with
        | { him = x; her = "Posh" } :: tail -> x
        | _ :: tail -> findDavid tail
        | [] -> failwith "Couldn't find David"
    

    如果模式匹配箭头右侧的表达式太大,则将其移至下一行,并从 match/| 缩进一级。

    // ✔️ OK
    match lam with
    | Var v -> 1
    | Abs(x, body) ->
        1 + sizeLambda body
    | App(lam1, lam2) ->
        sizeLambda lam1 + sizeLambda lam2
    

    类似于较大的 if 条件,如果 match 表达式跨多行或超过一行的默认容许长度,则 match 表达式应使用一个字符的缩进和一个新行。 封装较长的 match 表达式时,matchwith 关键字应该相符。

    // ✔️ OK, but better to refactor, see below
    match
        complexExpression a b && env.IsDevelopment()
        || someFunctionToCall
            aVeryLongParameterNameOne
            aVeryLongParameterNameTwo
            aVeryLongParameterNameThree 
    | X y -> y
    | _ -> 0
    

    但最好将长 match 表达式重构为 let 绑定或单独的函数:

    // ✔️ OK
    let performAction =
        complexExpression a b && env.IsDevelopment()
        || someFunctionToCall
            aVeryLongParameterNameOne
            aVeryLongParameterNameTwo
            aVeryLongParameterNameThree
    match performAction with
    | X y -> y
    | _ -> 0
    

    应避免对齐模式匹配的箭头。

    // ✔️ OK
    match lam with
    | Var v -> v.Length
    | Abstraction _ -> 2
    // ❌ Not OK, code formatters will reformat to the above by default
    match lam with
    | Var v         -> v.Length
    | Abstraction _ -> 2
    

    使用关键字 function 引入的模式匹配应该从上一行的开头缩进一级:

    // ✔️ OK
    lambdaList
    |> List.map (function
        | Abs(x, body) -> 1 + sizeLambda 0 body
        | App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
        | Var v -> 1)
    

    通常应避免在 letlet rec 定义的函数中使用 function,而应使用 match。 如果使用,则模式规则应与关键字 function 对齐:

    // ✔️ OK
    let rec sizeLambda acc =
        function
        | Abs(x, body) -> sizeLambda (succ acc) body
        | App(lam1, lam2) -> sizeLambda (sizeLambda acc lam1) lam2
        | Var v -> succ acc
    

    设置 try/with 表达式格式

    异常类型上的模式匹配应该缩进到与 with 相同的级别。

    // ✔️ OK
        if System.DateTime.Now.Second % 3 = 0 then
            raise (new System.Exception())
            raise (new System.ApplicationException())
    | :? System.ApplicationException ->
        printfn "A second that was not a multiple of 3"
    | _ ->
        printfn "A second that was a multiple of 3"
    

    为每个子句添加 |,除非只有一个子句:

    // ✔️ OK
        persistState currentState
    with ex ->
        printfn "Something went wrong: %A" ex
    // ✔️ OK
        persistState currentState
    with :? System.ApplicationException as ex ->
        printfn "Something went wrong: %A" ex
    // ❌ Not OK, see above for preferred formatting
        persistState currentState
    | ex ->
        printfn "Something went wrong: %A" ex
    // ❌ Not OK, see above for preferred formatting
        persistState currentState
    | :? System.ApplicationException as ex ->
        printfn "Something went wrong: %A" ex
    

    设置命名参数格式

    命名参数应该在 = 周围添加空格:

    // ✔️ OK
    let makeStreamReader x = new System.IO.StreamReader(path = x)
    // ❌ Not OK, spaces are necessary around '=' for named arguments
    let makeStreamReader x = new System.IO.StreamReader(path=x)
    

    例如,当使用可区分联合进行模式匹配时,命名模式的格式类似。

    type Data =
        | TwoParts of part1: string * part2: string
        | OnePart of part1: string
    // ✔️ OK
    let examineData x =
        match data with
        | OnePartData(part1 = p1) -> p1
        | TwoPartData(part1 = p1; part2 = p2) -> p1 + p2
    // ❌ Not OK, spaces are necessary around '=' for named pattern access
    let examineData x =
        match data with
        | OnePartData(part1=p1) -> p1
        | TwoPartData(part1=p1; part2=p2) -> p1 + p2
    

    设置变化表达式格式

    变化表达式 location <- expr 通常采用一行的格式。 如果需要多行格式,请将右侧表达式放在新行中。

    // ✔️ OK
    ctx.Response.Headers[HeaderNames.ContentType] <-
        Constants.jsonApiMediaType |> StringValues
    ctx.Response.Headers[HeaderNames.ContentLength] <-
        bytes.Length |> string |> StringValues
    // ❌ Not OK, code formatters will reformat to the above by default
    ctx.Response.Headers[HeaderNames.ContentType] <- Constants.jsonApiMediaType
                                                     |> StringValues
    ctx.Response.Headers[HeaderNames.ContentLength] <- bytes.Length
                                                       |> string
                                                       |> StringValues
    

    设置对象表达式格式

    对象表达式成员应与 member 对齐,并缩进一级。

    // ✔️ OK
    let comparer =
        { new IComparer<string> with
              member x.Compare(s1, s2) =
                  let rev (s: String) = new String (Array.rev (s.ToCharArray()))
                  let reversed = rev s1
                  reversed.CompareTo (rev s2) }
    

    你可能还希望使用 Stroustrup 样式:

    let comparer = { 
        new IComparer<string> with
            member x.Compare(s1, s2) =
                let rev (s: String) = new String(Array.rev (s.ToCharArray()))
                let reversed = rev s1
                reversed.CompareTo(rev s2)
    

    设置索引/切片表达式的格式

    索引表达式不应在左方括号和右方括号周围包含任何空格。

    // ✔️ OK
    let v = expr[idx]
    let y = myList[0..1]
    // ❌ Not OK
    let v = expr[ idx ]
    let y = myList[ 0 .. 1 ]
    

    这也适用于较早的 expr.[idx] 语法。

    // ✔️ OK
    let v = expr.[idx]
    let y = myList.[0..1]
    // ❌ Not OK
    let v = expr.[ idx ]
    let y = myList.[ 0 .. 1 ]
    

    设置带引号的表达式的格式

    如果带引号的表达式是多行表达式,则应将分隔符(<@@><@@@@>)放在单独的行上。

    // ✔️ OK
        let f x = x + 10
    // ❌ Not OK
    <@ let f x = x + 10
    

    在单行表达式中,应将分隔符放在表达式本身所在的行上。

    // ✔️ OK
    <@ 1 + 1 @>
    // ❌ Not OK
        1 + 1
    

    设置链式表达式的格式

    如果链式表达式(与 . 交织在一起的函数应用程序)很长,则将每个应用程序调用放在下一行。 在前导链接之后将链中的后续链接缩进一级。

    // ✔️ OK
        .CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
    // ✔️ OK
        .Wrap("git")
        .WithArguments(arguments)
        .WithWorkingDirectory(__SOURCE_DIRECTORY__)
        .ExecuteBufferedAsync()
        .Task
    

    如果前导链接是简单的标识符,则可以由多个链接组成。 例如,添加完全限定的命名空间。

    // ✔️ OK
    Microsoft.Extensions.Hosting.Host
        .CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
    

    后续链接还应包含简单的标识符。

    // ✔️ OK
    configuration.MinimumLevel
        .Debug()
        // Notice how `.WriteTo` does not need its own line.
        .WriteTo.Logger(fun loggerConfiguration ->
            loggerConfiguration.Enrich
                .WithProperty("host", Environment.MachineName)
                .Enrich.WithProperty("user", Environment.UserName)
                .Enrich.WithProperty("application", context.HostingEnvironment.ApplicationName))
    

    如果函数应用程序中的参数不适合该行的其余部分,请将每个参数放在下一行。

    // ✔️ OK
    WebHostBuilder()
        .UseKestrel()
        .UseUrls("http://*:5000/")
        .UseCustomCode(
            longArgumentOne,
            longArgumentTwo,
            longArgumentThree,
            longArgumentFour
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseStartup<Startup>()
        .Build()
    // ✔️ OK
    Cache.providedTypes
        .GetOrAdd(cacheKey, addCache)
        .Value
    // ❌ Not OK, formatting tools will reformat to the above
    Cache
        .providedTypes
        .GetOrAdd(
            cacheKey,
            addCache
        .Value
    

    函数应用程序中的 Lambda 参数应与开头 ( 在同一行开始。

    // ✔️ OK
    builder
        .WithEnvironment()
        .WithLogger(fun loggerConfiguration ->
            // ...
    // ❌ Not OK, formatting tools will reformat to the above
    builder
        .WithEnvironment()
        .WithLogger(
            fun loggerConfiguration ->
            // ...
    

    设置声明格式

    本部分介绍如何设置不同类型的声明的格式。

    在声明之间添加空行

    使用一个空行分隔顶级函数和类定义。 例如:

    // ✔️ OK
    let thing1 = 1+1
    let thing2 = 1+2
    let thing3 = 1+3
    type ThisThat = This | That
    // ❌ Not OK
    let thing1 = 1+1
    let thing2 = 1+2
    let thing3 = 1+3
    type ThisThat = This | That
    

    如果构造具有 XML 文档注释,请在注释前添加一个空行。

    // ✔️ OK
    /// This is a function
    let thisFunction() =
        1 + 1
    /// This is another function, note the blank line before this line
    let thisFunction() =
        1 + 1
    

    设置 let 和 member 声明的格式

    设置 letmember 声明的格式时,通常绑定的右侧要么在一行上,要么(如果太长)使用新行,并缩进一级。

    例如,以下示例符合要求:

    // ✔️ OK
    let a =
    foobar, long string
    // ✔️ OK
    type File =
        member this.SaveAsync(path: string) : Async<unit> =
            async {
                // IO operation
                return ()
    // ✔️ OK
    let c =
        { Name = "Bilbo"
          Age = 111
          Region = "The Shire" }
    // ✔️ OK
    let d =
        while f do
            printfn "%A" x
    

    这些不符合要求:

    // ❌ Not OK, code formatters will reformat to the above by default
    let a = """
    foobar, long string
    let d = while f do
        printfn "%A" x
    

    记录类型实例化也可以将括号放在其所在的行上:

    // ✔️ OK
    let bilbo =
            Name = "Bilbo"
            Age = 111
            Region = "The Shire" 
    

    你可能还更偏好使用 Stroustrup 样式,开头 { 与绑定名称位于同一行:

    // ✔️ OK
    let bilbo = {
        Name = "Bilbo"
        Age = 111
        Region = "The Shire"
    

    使用一个空行和文档分隔 member,并添加文档注释:

    // ✔️ OK
    /// This is a thing
    type ThisThing(value: int) =
        /// Gets the value
        member _.Value = value
        /// Returns twice the value
        member _.TwiceValue() = value*2
    

    可以(谨慎地)使用额外的空行来分隔相关功能组。 在一组相关的单行代码之间可以省略空行(例如,一组虚拟实现)。 在函数中慎用空行来指示逻辑部分。

    设置 function 和 member 参数的格式

    定义函数时,请在每个参数周围使用空格。

    // ✔️ OK
    let myFun (a: decimal) (b: int) c = a + b + c
    // ❌ Not OK, code formatters will reformat to the above by default
    let myFunBad (a:decimal)(b:int)c = a + b + c
    

    如果函数定义很长,请将参数放在新行中并缩进,以与后续参数的缩进级别保持一致。

    // ✔️ OK
    module M =
        let longFunctionWithLotsOfParameters
            (aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            // ... the body of the method follows
        let longFunctionWithLotsOfParametersAndReturnType
            (aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            : ReturnType =
            // ... the body of the method follows
        let longFunctionWithLongTupleParameter
                aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
                aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
                aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
            // ... the body of the method follows
        let longFunctionWithLongTupleParameterAndReturnType
                aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
                aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
                aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
            ) : ReturnType =
            // ... the body of the method follows
    

    这也适用于使用元组的成员、构造函数和参数:

    // ✔️ OK
    type TypeWithLongMethod() =
        member _.LongMethodWithLotsOfParameters
                aVeryLongParam: AVeryLongTypeThatYouNeedToUse,
                aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse,
                aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse
            // ... the body of the method
    // ✔️ OK
    type TypeWithLongConstructor
            aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
            aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
            aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse
        // ... the body of the class follows
    

    如果参数经过柯里化,请将 = 字符与任何返回类型一起放在新行中:

    // ✔️ OK
    type TypeWithLongCurriedMethods() =
        member _.LongMethodWithLotsOfCurriedParamsAndReturnType
            (aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            : ReturnType =
            // ... the body of the method
        member _.LongMethodWithLotsOfCurriedParams
            (aVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aSecondVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            (aThirdVeryLongParam: AVeryLongTypeThatYouNeedToUse)
            // ... the body of the method
    

    这是一种避免行过长(如果返回类型的名称可能很长)并且在添加参数时减少行损坏的方法。

    设置运算符声明格式

    可以选择在运算符定义周围使用空格:

    // ✔️ OK
    let ( !> ) x f = f x
    // ✔️ OK
    let (!>) x f = f x
    

    对于任何以 * 开头且具有多个字符的自定义运算符,需要在定义的开头添加一个空格以避免编译器歧义。 因此,我们建议只需在所有运算符的定义周围使用一个空格字符。

    设置记录声明的格式

    对于记录声明,默认情况下,应将类型定义中的 { 缩进四个空格,在同一行开始编写标签列表,并将成员(如果有)与 { 标记对齐:

    // ✔️ OK
    type PostalAddress =
        { Address: string
          City: string
          Zip: string }
    

    通常更偏好将括号放在其所在的行上,标签由额外的四个空格缩进:

    // ✔️ OK
    type PostalAddress =
            Address: string
            City: string
            Zip: string 
    

    你还可以将 { 放在类型定义第一行的末尾(Stroustrup 样式):

    // ✔️ OK
    type PostalAddress = {
        Address: string
        City: string
        Zip: string
    

    如果需要其他成员,请尽量不要使用 with/end

    // ✔️ OK
    type PostalAddress =
        { Address: string
          City: string
          Zip: string }
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    // ❌ Not OK, code formatters will reformat to the above by default
    type PostalAddress =
        { Address: string
          City: string
          Zip: string }
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    // ✔️ OK
    type PostalAddress =
            Address: string
            City: string
            Zip: string 
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    // ❌ Not OK, code formatters will reformat to the above by default
    type PostalAddress =
            Address: string
            City: string
            Zip: string 
            member x.ZipAndCity = $"{x.Zip} {x.City}"
    

    此样式规则的例外情况为是否根据 Stroustrup 样式设置记录格式。 在这种情况下,由于编译器规则,如果要实现接口或添加其他成员,则需要 with 关键字:

    // ✔️ OK
    type PostalAddress = {
        Address: string
        City: string
        Zip: string
    } with
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    // ❌ Not OK, this is currently invalid F# code
    type PostalAddress = {
        Address: string
        City: string
        Zip: string
    member x.ZipAndCity = $"{x.Zip} {x.City}"
    

    如果为记录字段添加了 XML 文档,首选 AlignedStroustrup 样式,并且应在成员之间添加额外的空格:

    // ❌ Not OK - putting { and comments on the same line should be avoided
    type PostalAddress =
        { /// The address
          Address: string
          /// The city
          City: string
          /// The zip code
          Zip: string }
        /// Format the zip code and the city
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    // ✔️ OK
    type PostalAddress =
            /// The address
            Address: string
            /// The city
            City: string
            /// The zip code
            Zip: string
        /// Format the zip code and the city
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    // ✔️ OK - Stroustrup Style
    type PostalAddress = {
        /// The address
        Address: string
        /// The city
        City: string
        /// The zip code
        Zip: string
    } with
        /// Format the zip code and the city
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    

    如果要在记录中声明接口实现或成员,最好将开始标记放在一个新行中,将结束标记放在一个新行中:

    // ✔️ OK
    // Declaring additional members on PostalAddress
    type PostalAddress =
            /// The address
            Address: string
            /// The city
            City: string
            /// The zip code
            Zip: string
        member x.ZipAndCity = $"{x.Zip} {x.City}"
    // ✔️ OK
    type MyRecord =
            /// The record field
            SomeField: int
        interface IMyInterface
    

    这些相同的规则也适用于匿名记录类型别名。

    设置可区分联合声明的格式

    对于可区分联合声明,将类型定义中的 | 缩进四个空格:

    // ✔️ OK
    type Volume =
        | Liter of float
        | FluidOunce of float
        | ImperialPint of float
    // ❌ Not OK
    type Volume =
    | Liter of float
    | USPint of float
    | ImperialPint of float
    

    如果只有一个短联合,则可以省略前导 |

    // ✔️ OK
    type Address = Address of string
    

    对于较长的联合或多行联合,保留 |,并将每个联合字段放在一个新行中,并在每行的末尾使用 * 分隔。

    // ✔️ OK
    [<NoEquality; NoComparison>]
    type SynBinding =
        | SynBinding of
            accessibility: SynAccess option *
            kind: SynBindingKind *
            mustInline: bool *
            isMutable: bool *
            attributes: SynAttributes *
            xmlDoc: PreXmlDoc *
            valData: SynValData *
            headPat: SynPat *
            returnInfo: SynBindingReturnInfo option *
            expr: SynExpr *
            range: range *
            seqPoint: DebugPointAtBinding
    

    添加文档注释时,在每个 /// 注释之前使用空行。

    // ✔️ OK
    /// The volume
    type Volume =
        /// The volume in liters
        | Liter of float
        /// The volume in fluid ounces
        | FluidOunce of float
        /// The volume in imperial pints
        | ImperialPint of float
    

    设置文本声明的格式

    使用 Literal 属性的 F# 文本应将该属性放在其自己的行上,并使用 PascalCase 命名法:

    // ✔️ OK
    [<Literal>]
    let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
    [<Literal>]
    let MyUrl = "www.mywebsitethatiamworkingwith.com"
    

    避免将属性与值放在同一行中。

    设置模块声明的格式

    本地模块中的代码必须相对于 module 缩进,但顶级模块中的代码不应缩进。 命名空间元素不需要缩进。

    // ✔️ OK - A is a top-level module.
    module A
    let function1 a b = a - b * b
    
    // ✔️ OK - 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
    

    设置 do 声明的格式

    在类型声明、模块声明和计算表达式中,使用 dodo! 有时需要进行副作用运算。 当这些内容跨多行时,使用缩进和新行来保持缩进与 let/let! 一致。 下面是在类中使用 do 的示例:

    // ✔️ OK
    type Foo() =
        let foo =
            fooBarBaz
            |> loremIpsumDolorSitAmet
            |> theQuickBrownFoxJumpedOverTheLazyDog
            fooBarBaz
            |> loremIpsumDolorSitAmet
            |> theQuickBrownFoxJumpedOverTheLazyDog
    // ❌ Not OK - notice the "do" expression is indented one space less than the `let` expression
    type Foo() =
        let foo =
            fooBarBaz
            |> loremIpsumDolorSitAmet
            |> theQuickBrownFoxJumpedOverTheLazyDog
        do fooBarBaz
           |> loremIpsumDolorSitAmet
           |> theQuickBrownFoxJumpedOverTheLazyDog
    

    下面是使用两个空格进行缩进的 do! 示例(因为对于 do!,使用四个空格进行缩进时,碰巧这两种方法之间没有区别):

    // ✔️ OK
    async {
      let! foo =
        fooBarBaz
        |> loremIpsumDolorSitAmet
        |> theQuickBrownFoxJumpedOverTheLazyDog
        fooBarBaz
        |> loremIpsumDolorSitAmet
        |> theQuickBrownFoxJumpedOverTheLazyDog
    // ❌ Not OK - notice the "do!" expression is indented two spaces more than the `let!` expression
    async {
      let! foo =
        fooBarBaz
        |> loremIpsumDolorSitAmet
        |> theQuickBrownFoxJumpedOverTheLazyDog
      do! fooBarBaz
          |> loremIpsumDolorSitAmet
          |> theQuickBrownFoxJumpedOverTheLazyDog
    

    设置计算表达式操作的格式

    在针对计算表达式创建自定义操作时,建议使用 camelCase 命名法:

    // ✔️ OK
    type MathBuilder() =
        member _.Yield _ = 0
        [<CustomOperation("addOne")>]
        member _.AddOne (state: int) =
            state + 1
        [<CustomOperation("subtractOne")>]
        member _.SubtractOne (state: int) =
            state - 1
        [<CustomOperation("divideBy")>]
        member _.DivideBy (state: int, divisor: int) =
            state / divisor
        [<CustomOperation("multiplyBy")>]
        member _.MultiplyBy (state: int, factor: int) =
            state * factor
    let math = MathBuilder()
    let myNumber =
        math {
            addOne
            addOne
            addOne
            subtractOne
            divideBy 2
            multiplyBy 10
    

    要建模的域最终应完成命名约定。 如果习惯使用其他约定,则应改为使用该约定。

    如果表达式的返回值是计算表达式,最好将计算表达式关键字名称放在其所在的行中:

    // ✔️ OK
    let foo () = 
        async {
            let! value = getValue()
            do! somethingElse()
            return! anotherOperation value 
    

    你可能还更偏好将计算表达式与绑定名称放在同一行:

    // ✔️ OK
    let foo () = async {
        let! value = getValue()
        do! somethingElse()
        return! anotherOperation value 
    

    无论你喜欢哪种方式,都应致力于在整个代码库中保持一致。 借助格式化程序,可以指定此首选项以保持一致。

    设置类型和类型注释的格式

    本部分介绍如何设置类型和类型注释的格式。 这包括设置具有 .fsi 扩展名的签名文件的格式。

    对于类型,首选泛型 (Foo<T>) 的前缀语法,但有一些特定的例外

    F# 允许编写泛型类型的后缀样式(例如,int list)和前缀样式(例如,list<int>)。 后缀样式只能与单个类型参数一起使用。 始终首选 .NET 样式,但这五个特定类型除外:

  • 对于 F# 列表,使用后缀形式:int list,而不是 list<int>
  • 对于 F# 选项,使用后缀形式:int option,而不是 option<int>
  • 对于 F# 值选项,使用后缀形式:int voption,而不是 voption<int>
  • 对于 # 数组,请使用语法名称 int[],而不是 int arrayarray<int>
  • 对于引用单元格,使用 int ref,而不是 ref<int>Ref<int>
  • 对于所有其他类型,使用前缀形式。

    设置函数类型格式

    定义函数的签名时,请在 -> 符号周围使用空格:

    // ✔️ OK
    type MyFun = int -> int -> string
    // ❌ Not OK
    type MyFunBad = int->int->string
    

    设置值和参数类型注释的格式

    定义包含类型注释的值或参数时,请在 : 符号之后使用空格,但不能在之前使用:

    // ✔️ OK
    let complexFunction (a: int) (b: int) c = a + b + c
    let simpleValue: int = 0 // Type annotation for let-bound value
    type C() =
        member _.Property: int = 1
    // ❌ Not OK
    let complexFunctionPoorlyAnnotated (a :int) (b :int) (c:int) = a + b + c
    let simpleValuePoorlyAnnotated1:int = 1
    let simpleValuePoorlyAnnotated2 :int = 2
    

    设置多行类型批注的格式

    类型批注很长或是多行时,将其放在下一行,并缩进一级。

    type ExprFolder<'State> =
        { exprIntercept: 
            ('State -> Expr -> 'State) -> ('State -> Expr -> 'State -> 'State -> Exp -> 'State }
    let UpdateUI
        (model:
    #if NETCOREAPP2_1
            ITreeModel
    #else
            TreeModel
    #endif
        (info: FileInfo) =
        // code
    let f
                a: Second
                b: Metre
                c: Kilogram
                d: Ampere
                e: Kelvin
                f: Mole
                g: Candela
    type Sample
            input: 
                LongTupleItemTypeOneThing * 
                LongTupleItemTypeThingTwo * 
                LongTupleItemTypeThree * 
                LongThingFour * 
                LongThingFiveYow
        class
    

    对于内联匿名记录类型,还可以使用 Stroustrup 样式:

    let f
        (x: {|
            x: int
            y: AReallyLongTypeThatIsMuchLongerThan40Characters
    

    设置返回类型批注的格式

    在函数或成员返回类型批注中,在 : 符号前后使用空格:

    // ✔️ OK
    let myFun (a: decimal) b c : decimal = a + b + c
    type C() =
        member _.SomeMethod(x: int) : int = 1
    // ❌ Not OK
    let myFunBad (a: decimal) b c:decimal = a + b + c
    let anotherFunBad (arg: int): unit = ()
    type C() =
        member _.SomeMethodBad(x: int): int = 1
    

    设置签名中类型的格式

    在签名中编写完整的函数类型时,有时需要将参数拆分为多行。 返回类型始终进行缩进。

    对于元组函数,参数由 * 分隔,并放置在每行的末尾。

    例如,请考虑具有以下实现的函数:

    let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
    

    在对应的签名文件(具有 .fsi 扩展名)中,当需要多行格式时,可将函数设置为以下格式:

    // ✔️ OK
    val SampleTupledFunction:
        arg1: string *
        arg2: string *
        arg3: int *
        arg4: int ->
            int list
    

    同样,请考虑经过柯里化的函数:

    let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
    

    在对应的签名文件中,-> 位于每行的末尾:

    // ✔️ OK
    val SampleCurriedFunction:
        arg1: string ->
        arg2: string ->
        arg3: int ->
        arg4: int ->
            int list
    

    同样,请考虑接受柯里化和元组参数的组合的函数:

    // Typical call syntax:
    let SampleMixedFunction
            (arg1, arg2)
            (arg3, arg4, arg5)
            (arg6, arg7)
            (arg8, arg9, arg10) = ..
    

    在相应的签名文件中,元组前面的类型会进行缩进

    // ✔️ OK
    val SampleMixedFunction:
        arg1: string *
        arg2: string ->
            arg3: string *
            arg4: string *
            arg5: TType ->
                arg6: TType *
                arg7: TType ->
                    arg8: TType *
                    arg9: TType *
                    arg10: TType ->
                        TType list
    

    相同的规则适用于类型签名中的成员:

    type SampleTypeName =
        member ResolveDependencies:
            arg1: string *
            arg2: string ->
                string
    

    设置显式泛型类型参数和约束的格式

    下面的指南适用于函数定义、成员定义、类型定义和函数应用程序。

    如果泛型类型参数和约束不太长,请将它们保留在一行中:

    // ✔️ OK
    let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
        // function body
    

    如果泛型类型参数/约束和函数参数均不适用,但只有类型参数/约束适用,则将参数放在新行中:

    // ✔️ OK
    let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
        param
        // function body
    

  •