Рекомендации по форматированию кода F#

В этой статье приведены рекомендации по форматированию кода таким образом, чтобы код F# был таким:

  • Более разборчивый
  • В соответствии с соглашениями, применяемыми средствами форматирования в Visual Studio Code и других редакторах
  • Аналогично другому коду в Сети

См. также соглашения о написании кода и рекомендации по проектированию компонентов, которые также охватывают соглашения об именовании.

Автоматическое форматирование кода

Модуль форматирования кода Fantomas — это стандартное средство сообщества F# для автоматического форматирования кода. Параметры по умолчанию соответствуют этому руководству по стилю.

Настоятельно рекомендуется использовать этот модуль форматирования кода. В командах F# спецификации форматирования кода должны быть согласованы и кодированы с точки зрения файла согласованных параметров для модуля форматирования кода, проверенного в репозитории команд.

Общие правила форматирования

F# по умолчанию использует значительные пробелы и учитывает пробелы. Следующие рекомендации предназначены для предоставления рекомендаций о том, как жонглировать некоторые проблемы, которые это может навязать.

Использование пробелов без вкладок

Если требуется отступ, необходимо использовать пробелы, а не вкладки. Код F# не использует вкладки, и компилятор выдает ошибку, если символ табуляции встречается вне строкового литерала или комментария.

Использование согласованной отступы

При отступе требуется по крайней мере одно пространство. Ваша организация может создавать стандарты кодирования, чтобы указать количество пробелов, используемых для отступа; два, три или четыре пробела отступа на каждом уровне, где происходит отступ, является типичным.

Рекомендуется четверо пробелов на отступ.

Тем не более того, отступ программ является субъективным вопросом. Варианты в порядке, но первое правило, которое следует следовать, — это согласованность отступа. Выберите общепринятый стиль отступа и систематически используйте его во всей базе кода.

Избегайте форматирования, чувствительного к длине имени

Старайтесь избегать отступов и выравнивания, чувствительных к именованию:

// ✔️ OK
let myLongValueName =
    someExpression
    |> anotherExpression

// ❌ Not OK
let myLongValueName = someExpression
                      |> anotherExpression

// ✔️ OK
let myOtherVeryLongValueName =
    match
        someVeryLongExpressionWithManyParameters
            parameter1
            parameter2
            parameter3
        with
    | 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)

Не пропускайте пробелы при вызове с несколькими аргументами curried:

// ✔️ 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)

В соглашениях о форматировании по умолчанию пространство не добавляется при применении прописных методов к аргументам кортежа. Это связано с тем, что они часто используются с текучим программированием:

// ✔️ 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

Форматирование лямбда-выражений

Если лямбда-выражение используется в качестве аргумента в многостроковом выражении и за ним следуют другие аргументы, поместите тело лямбда-выражения на новую строку с отступом на один уровень:

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

Если лямбда-аргумент является последним аргументом в приложении-функции, поместите все аргументы до стрелки в одной строке.

// ✔️ 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}")

Относиться к лямбда-лямбда-сходно.

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

Если перед лямбда-отступом всех аргументов с одним уровнем есть много ведущих или многострочный аргумент.

// ✔️ OK
functionName
    arg1
    arg2
    arg3
    (fun arg4 ->
        bodyExpr)

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

Если тело лямбда-выражения имеет длину нескольких строк, следует рассмотреть возможность рефакторинга в локально ограниченной функции.

Если конвейеры включают лямбда-выражения, каждое лямбда-выражение обычно является последним аргументом на каждом этапе конвейера:

// ✔️ 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}")

Если аргументы лямбда-выражения не помещаются в одну строку или являются многострочных, поместите их на следующую строку с отступом на один уровень.

// ✔️ OK
fun
    (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 () =
    fun
        (input:
            {| name: string
               amount: Amount
               isIncome: bool
               created: string |}) ->
         // foo
         bar ()

// ❌ Not OK, code formatters will reformat to the above to avoid the vanity alignment.
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

Добавление символа пробела после того, как - оператор может привести к путанице для других пользователей.

Разделение двоичных операторов по пробелам. Выражения Infix в порядке для создания строк в одном столбце:

// ✔️ 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]

Форматирование, если выражения

Отступы условного выражения зависят от размера и сложности выражений, составляющих их. Записывайте их в одной строке, когда:

  • cond, e1и e2 короткие
  • e1 и e2 не if/then/else являются сами выражениями.
// ✔️ OK
if cond then e1 else e2

Если другое выражение отсутствует, рекомендуется никогда не записывать все выражение в одной строке. Это позволяет отличить императивный код от функционального.

// ✔️ OK
if a then
    ()

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

Если любое из выражений является многострочный или многострочный.if/then/else

// ✔️ OK
if cond then
    e1
else
    e2

Несколько условных условий с elif и отступами находятся в той же области, что if и else при выполнении правил одной строкиif/then/else.

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

Если любое из условий или выражений является многострочный, все if/then/else выражение является многострочный:

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

Если условие является многострочный или превышает допустимость по умолчанию для одной строки, выражение условия должно использовать одну отступ и новую строку. Ключевое if слово и then ключевое слово должны выравнивать при инкапсуле выражения длинного условия.

// ✔️ OK, but better to refactor, see below
if
    complexExpression a b && env.IsDevelopment()
    || someFunctionToCall
        aVeryLongParameterNameOne
        aVeryLongParameterNameTwo
        aVeryLongParameterNameThree 
then
        e1
    else
        e2

// ✔️The same applies to nested `elif` or `else if` expressions
if a then
    b
elif
    someLongFunctionCall
        argumentOne
        argumentTwo
        argumentThree
        argumentFour
then
    c
else if
    someOtherLongFunctionCall
        argumentOne
        argumentTwo
        argumentThree
        argumentFour
then
    d

Однако лучше выполнить рефакторинг длинных условий для привязки или отдельной функции:

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

if performAction then
    e1
else
    e2

Форматирование выражений вариантов объединения

Применение различаемых вариантов объединения соответствует тем же правилам, что и приложения функций и методов. Это связано с тем, что имя заглавно, модули форматирования кода удаляют пробел перед кортежем:

// ✔️ 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 с пробелами вокруг :: оператора (:: это оператор infix, следовательно, окруженный пробелами).

Список и массивы, объявленные в одной строке, должны иметь пробел после открывающей скобки и перед закрывающей скобкой:

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

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

Всегда используйте по крайней мере одно пространство между двумя отдельными операторами фигурных скобок. Например, оставьте пробел между a [ и a {.

// ✔️ 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 |] |]

Как и в случае с записями, объявление открывающих и закрывающих квадратных скобок в собственной строке сделает перемещение кода вокруг и трубопровода в функции проще.

При создании массивов и списков программным способом предпочитайте, ->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 в ситуациях, когда данные могут быть созданы условно или могут быть вычисляются последовательные выражения. Не следует пропускать эти yield ключевые слова, если только не требуется скомпилировать более раннюю версию языка F#:

// ✔️ 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"] }

Выражения поля длинных записей должны использовать новую строку и иметь отступ от открытия {:

{ A = a
  B =
    someFunctionCall
        arg1
        arg2
        // ...
        argX
  C = c }

{ Размещение и } размещение новых строк с содержимым отступа возможно, однако форматировщики кода могут переформатировать его по умолчанию:

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

// ❌ Not preferred, code formatters will reformat to the above by default
let rainbow =
    {
        Boss1 = "Jeffrey"
        Boss2 = "Jeffrey"
        Boss3 = "Jeffrey"
        Lackeys = ["Zippy"; "George"; "Bungle"]
    }

Те же правила применяются к элементам списка и массива.

Форматирование выражений записи копирования и обновления

Выражение записи копирования и обновления по-прежнему является записью, поэтому аналогичные рекомендации применяются.

Короткие выражения могут помещаться в одну строку:

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

Более длинные выражения должны использовать новые строки:

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

Может потребоваться выделить отдельные строки для фигурных скобок и отступ одной области справа с выражением, однако формататоры кода могут переформатировать его. В некоторых особых случаях, таких как упаковка значения с необязательным без круглых скобок, может потребоваться сохранить фигурную скобку в одной строке:

// ✔️ OK
let newState =
    { state with
          Foo = Some { F1 = 0; F2 = "" } }

// ❌ Not OK, code formatters will reformat to the above by default
let newState =
    {
        state with
            Foo =
                Some {
                    F1 = 0
                    F2 = ""
                }
    }

Сопоставление шаблонов форматирования

| Используйте для каждого предложения соответствия без отступа. Если выражение короткое, можно использовать одну строку, если каждая часть выражения также проста.

// ✔️ 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

Аналогично большим условиям, если выражение соответствия является многостроочным или превышает допустимость по умолчанию однострочного выражения, выражение соответствия должно использовать одну отступ и новую строку. Ключевое слово и with ключевое match слово должны выравнивать при инкапсуле выражения длинного совпадения.

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

Однако лучше выполнить рефакторинг выражений длинных совпадений в функцию let binding или separate:

// ✔️ 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)

Использование в функциях, определенных letfunction или let rec в целом, следует избегать использования в пользу 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 expressions

Сопоставление шаблонов для типа исключения должно быть отступом на том же уровне, что и with.

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

Добавьте предложение | для каждого предложения, за исключением случаев, когда существует только одно предложение:

// ✔️ OK
try
    persistState currentState
with ex ->
    printfn "Something went wrong: %A" ex

// ✔️ OK
try
    persistState currentState
with :? System.ApplicationException as ex ->
    printfn "Something went wrong: %A" ex

// ❌ Not OK, see above for preferred formatting
try
    persistState currentState
with
| ex ->
    printfn "Something went wrong: %A" ex

// ❌ Not OK, see above for preferred formatting
try
    persistState currentState
with
| :? 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) }

Форматирование выражений индекса и среза

Выражения индекса не должны содержать пробелы вокруг открывающих и закрывающих квадратных скобок.

// ✔️ 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
    f 20
@>

// ❌ Not OK
<@ let f x = x + 10
   f 20
@>

В однострочных выражениях символы разделителя должны размещаться в той же строке, что и само выражение.

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

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

Объявления форматирования

В этом разделе рассматриваются объявления форматирования различных типов.

Добавление пустых строк между объявлениями

Разделите определения функций верхнего уровня и классов одной пустой строкой. Например:

// ✔️ 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 и членов

При форматировании let и member объявлениях правая сторона привязки либо идет по одной строке, либо (если слишком длинно) переходит на новый отступ на один уровень.

Например, следующие требования соответствуют требованиям.

// ✔️ 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
"""

type File =
    member this.SaveAsync(path: string) : Async<unit> = async {
        // IO operation
        return ()
    }

let c = {
    Name = "Bilbo"
    Age = 111
    Region = "The Shire"
}

let d = while f do
    printfn "%A" x

Разделите участников с одной пустой строкой и документом и добавьте комментарий к документации:

// ✔️ OK

/// This is a thing
type ThisThing(value: int) =

    /// Gets the value
    member _.Value = value

    /// Returns twice the value
    member _.TwiceValue() = value*2

Дополнительные пустые строки можно использовать (экономно) для разделения групп связанных функций. Пустые строки могут быть пропущены между кучей связанных одностроковых линий (например, набор фиктивных реализаций). Используйте пустые строки в функциях, экономно, чтобы указать логические разделы.

Форматирование аргументов функции и члена

При определении функции используйте пробелы вокруг каждого аргумента.

// ✔️ 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 }
    member x.ZipAndCity = $"{x.Zip} {x.City}"

Не размещайте { его в конце строки объявления типа и не используйте with/end для членов, которые являются избыточными.

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

Когда XML-документация добавляется для полей записей, она становится нормальной для отступа и добавления пробелов:

// ✔️ 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
// 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}"

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

Форматирование объявлений литерала

Литералы F# с помощью атрибута Literal должны размещать атрибут в собственной строке и использовать именование PascalCase:

// ✔️ OK

[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__

[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"

Избегайте размещения атрибута в той же строке, что и значение.

Форматирование объявлений модуля

Код в локальном модуле должен быть отступен относительно модуля, но код в модуле верхнего уровня не должен быть отступом. Элементы пространства имен не должны быть отступами.

// ✔️ 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

В объявлениях типов, объявлениях модулей и выражениях вычислений использование do или do! иногда требуется для побочных операций. Если они охватывают несколько строк, используйте отступ и новую строку, чтобы обеспечить согласованность отступов.let/let! Ниже приведен пример использования do в классе:

// ✔️ OK
type Foo () =
    let foo =
        fooBarBaz
        |> loremIpsumDolorSitAmet
        |> theQuickBrownFoxJumpedOverTheLazyDog

    do
        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

  do!
    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
    }

Домен, который моделиируется, должен в конечном итоге управлять соглашением об именовании. Если идиоматично использовать другое соглашение, вместо этого следует использовать это соглашение.

Форматирование типов и заметок типов

В этом разделе рассматриваются типы форматирования и заметки типов. Сюда входят файлы сигнатуры форматирования с расширением .fsi .

Для типов предпочитать синтаксис префикса для универсальных шаблонов (Foo<T>) с некоторыми конкретными исключениями

F# позволяет создавать универсальные типы (например, int list) и стиль префикса (например, list<int>). Стиль постфикса можно использовать только с одним аргументом типа. Всегда предпочитать стиль .NET, за исключением пяти конкретных типов:

  1. Для списков F# используйте форму постфикса: int list а не list<int>.
  2. Для параметров F# используйте форму постфикса: int option а не option<int>.
  3. Для параметров значения F# используйте форму постфикса: int voption а не voption<int>.
  4. Для массивов F# используйте синтаксическое имя int[] , а не int arrayarray<int>.
  5. Для ссылочных ячеек используйте 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
    (x:
        {|
            a: Second
            b: Metre
            c: Kilogram
            d: Ampere
            e: Kelvin
            f: Mole
            g: Candela
        |})
    =
    x.a

type Sample
    (
        input: 
            LongTupleItemTypeOneThing * 
            LongTupleItemTypeThingTwo * 
            LongTupleItemTypeThree * 
            LongThingFour * 
            LongThingFiveYow
    ) =
    class
    end

Форматирование заметок возвращаемого типа

В заметках к типу возвращаемого типа функции или члена используйте пробел до и после символа : :

// ✔️ 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

Если параметры или ограничения типа слишком длинные, разорвать и выравнивать их, как показано ниже. Сохраняйте список параметров типа в той же строке, что и функция, независимо от ее длины. Для ограничений поместите when на первую строку и сохраните каждое ограничение в одной строке независимо от его длины. Поместите > в конец последней строки. Отступ ограничений на один уровень.

// ✔️ OK
let inline f< ^T1, ^T2
    when ^T1 : (static member Foo1: unit -> ^T2)
    and ^T2 : (member Foo2: unit -> int)
    and ^T2 : (member Foo3: string -> ^T1 option)>
    arg1
    arg2
    =
    // function body

Если параметры и ограничения типа разбиты, но отсутствуют обычные параметры функции, поместите = ее в новую строку независимо от следующего:

// ✔️ OK
let inline f<^T1, ^T2
    when ^T1 : (static member Foo1: unit -> ^T2)
    and ^T2 : (member Foo2: unit -> int)
    and ^T2 : (member Foo3: string -> ^T1 option)>
    =
    // function body

Те же правила применяются для приложений-функций:

// ✔️ OK
myObj
|> Json.serialize<
    {| child: {| displayName: string; kind: string |}
       newParent: {| id: string; displayName: string |}
       requiresApproval: bool |}>

// ✔️ OK
Json.serialize<
    {| child: {| displayName: string; kind: string |}
       newParent: {| id: string; displayName: string |}
       requiresApproval: bool |}>
    myObj

Наследование форматирования

Аргументы конструктора базового класса отображаются в списке аргументов в предложении inherit . inherit Поместите предложение в новую строку с отступом на один уровень.

type MyClassBase(x: int) =
   class
   end

// ✔️ OK
type MyClassDerived(y: int) =
   inherit MyClassBase(y * 2)

// ❌ Not OK
type MyClassDerived(y: int) = inherit MyClassBase(y * 2)

Если конструктор длинный или многострочный, поместите их в следующую строку с отступом на один уровень.
Отформатируйте этот многостровой конструктор в соответствии с правилами многострочных приложений-функций.

type MyClassBase(x: string) =
   class
   end

// ✔️ OK
type MyClassDerived(y: string) =
    inherit 
        MyClassBase(
            """
            very long
            string example
            """
        )
        
// ❌ Not OK
type MyClassDerived(y: string) =
    inherit MyClassBase(
        """
        very long
        string example
        """)

Несколько конструкторов

Если предложение inherit является частью записи, поместите его в ту же строку, если она коротка. И поместите его на следующую строку, отступ на один уровень, если он длинный или многострочный.

type BaseClass =
    val string1 : string
    new () = { string1 = "" }
    new (str) = { string1 = str }

type DerivedClass =
    inherit BaseClass

    val string2 : string
    new (str1, str2) = { inherit BaseClass(str1); string2 = str2 }
    new () = 
        { inherit 
            BaseClass(
                """
                very long
                string example
                """
            )
          string2 = str2 }

Атрибуты форматирования

Атрибуты помещаются над конструкцией:

// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...

// ✔️ OK
[<RequireQualifiedAccess>]
module M =
    let f x = x

// ✔️ OK
[<Struct>]
type MyRecord =
    { Label1: int
      Label2: string }

Они должны пройти после любой XML-документации:

// ✔️ OK

/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
    let f x = x

Форматирование атрибутов для параметров

Атрибуты также можно поместить в параметры. В этом случае поместите его в ту же строку, что и параметр, и перед именем:

// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
    member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)

Форматирование нескольких атрибутов

Если к конструкции, которая не является параметром, применяется несколько атрибутов, поместите каждый атрибут в отдельную строку:

// ✔️ OK

[<Struct>]
[<IsByRefLike>]
type MyRecord =
    { Label1: int
      Label2: string }

При применении к параметру поместите атрибуты в одну строку и разделите их ; разделителем.

Благодарности

Эти рекомендации основаны на комплексном руководстве по соглашениям о форматировании F#anh-Dung Phan.