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

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

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

Эти рекомендации основаны на подробном руководстве по форматированию F # с помощью АНХ-dung Phan.

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

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

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
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:

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

Аналогичным образом считайте лямбда-выражение match.

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

Следующие операторы определены в стандартной библиотеке 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

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

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

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

// ✔️ OK
if cond then
    e1
else
    e2

Несколько условий с elif и else располагаются с отступом в той же области, что и if при соблюдении правил из выражений одной строки 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

Если условие является длинным, then ключевое слово по-прежнему размещается в конце выражения.

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

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

// ✔️ 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 с пробелами вокруг :: оператора ( :: является оператором инфиксные, таким образом заключенным в пробелы).

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

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

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

При создании массивов и списков программным способом предпочтительнее, -> 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 = ""
                }
    }

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

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

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

Не следует выключать стрелки соответствия шаблону.

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

Использование function в функциях, определенных или, в let 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

Сопоставление шаблонов для типа исключения должно иметь отступ на том же уровне, что и 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"

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

// ✔️ 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, no spaces 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, no spaces 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 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 doc, добавьте пустую строку перед комментарием.

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

Для более длинного или многострочного объединения следует | размещать и располагать каждое поле Union на новой строке с разделителем * в конце каждой строки.

// ✔️ 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 array или array<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

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

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

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

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