Compartilhar via


Diretrizes de formatação de código do F#

Este artigo oferece diretrizes para formatar seu código para que seu código F# seja:

  • Mais legível
  • De acordo com as convenções aplicadas por ferramentas de formatação no Visual Studio Code e outros editores
  • Semelhante a outros códigos online

Consulte também as convenções de codificação e as diretrizes de design de componente, que também incluem convenções de nomenclatura.

Formatação automática de código

O formatador de código Fantomas é a ferramenta padrão da comunidade F# para formatação automática de código. As configurações padrão correspondem a este guia de estilo.

É altamente recomendável o uso desse formatador de código. Dentro das equipes do F#, as especificações de formatação de código devem ser acordadas e codificadas em um arquivo de configurações consensual para o formatador de código, que deve ser adicionado ao repositório da equipe.

Regras gerais para formatação

O F# usa espaço em branco significativo por padrão e é sensível ao espaço em branco. As diretrizes a seguir têm como objetivo fornecer instruções sobre como lidar com alguns desafios que podem ser impostos.

Use espaços e não tabulações

Quando o recuo é necessário, use os espaços e não tabulações. O código F# não usa tabulações e o compilador gerará um erro se um caractere de tabulação for encontrado fora de um literal de string ou comentário.

Usar indentação consistente

Ao identar, pelo menos um espaço é necessário. Sua organização pode criar padrões de codificação para especificar o número de espaços a serem usados para recuo; são normais dois, três ou quatro espaços de recuo em cada nível de recuo.

Recomendamos quatro espaços por indentação.

Dito isto, a indentação de códigos é um assunto subjetivo. As variações acontecem, mas a primeira regra que você deve seguir é a consistência da indentação. Escolha um estilo de recuo geralmente aceito e use-o sistematicamente em toda a base de código.

Evite formatação que seja sensível ao tamanho do nome

Procure evitar recuos e alinhamentos que sejam sensíveis à nomenclatura:

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

Os principais motivos para evitar isso são:

  • Código importante é movido para a direita
  • O espaço disponível para o código real está menor.
  • Renomear pode interromper o alinhamento

Evite espaço em branco desnecessário

Evite espaço em branco desnecessário no código F#, exceto quando descrito neste guia de estilo.

// ✔️ OK
spam (ham 1)

// ❌ Not OK
spam ( ham 1 )

Formatação de comentários

Prefira vários comentários com duas barras em vez de comentários de bloco.

// 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.
*)

Os comentários devem ter a primeira letra maiúscula e ter frases bem formadas.

// ✔️ A good comment.
let f x = x + 1 // Increment by one.

// ❌ two poor comments
let f x = x + 1 // plus one

Para formatar comentários da documentação XML, consulte "Formatação de declarações" abaixo.

Formatação de expressões

Esta seção discute a formatação de expressões de diferentes tipos.

Formatação de expressões de cadeia de caracteres

Literais de strings e strings interpoladas podem ser deixadas em uma única linha, independentemente de quão longa seja a linha.

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

Não são encorajadas expressões interpoladas de multilinhas. Em vez disso, associe o resultado da expressão a um valor e use-o na cadeia de caracteres interpolada.

Formatação de expressões de tuplas

Uma instanciação de tupla deve estar entre parênteses e as vírgulas delimitantes dentro dela devem ser seguidas por um único espaço, por exemplo: (1, 2), (x, y, z).

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

É comumente aceito omitir parênteses nos padrões correspondentes de tuplas:

// ✔️ OK
let (x, y) = z
let x, y = z

// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1

Também é comumente aceito omitir parênteses se a tupla for o valor retornado de uma função:

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

Em resumo, prefira instanciações de tupla entre parênteses, mas ao usar tuplas para correspondência de padrões ou um valor de retorno, é considerado aceitável evitar parênteses.

Formatação de expressões de aplicativo

Ao formatar um aplicativo de função ou método, os argumentos são fornecidos na mesma linha quando a largura da linha permite:

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

Omitir parênteses, a menos que os argumentos exijam:

// ✔️ OK
someFunction1 x.IngredientName

// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)

// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)

Não omitir espaços ao invocar vários argumentos na forma 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)

Em convenções de formatação padrão, um espaço é adicionado ao aplicar funções minúsculas a argumentos em tupla ou entre parênteses (mesmo quando um único argumento é usado):

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

Em convenções de formatação padrão, nenhum espaço é adicionado ao aplicar métodos capitalizados a argumentos em forma de tupla. Isso ocorre porque geralmente são usados na programação fluente.

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

Essas mesmas convenções de formatação se aplicam à correspondência de padrões. Valores de estilo F# com formatação consistente:

// ✔️ OK - Consistent formatting for expressions and patterns
let result = Some(value)

match result with
| Some(x) -> x
| None -> 0

Talvez seja necessário passar argumentos para uma função em uma nova linha por questões de legibilidade ou porque a lista ou os nomes dos argumentos são muito longos. Nesse caso, recuar um nível:

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

Quando a função recebe um único argumento em uma tupla multilinha, coloque cada argumento em uma nova linha:

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

Se as expressões de argumento forem curtas, separe os argumentos com espaços e mantenha-os em uma linha.

// ✔️ OK
let person = new Person(a1, a2)

// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)

// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)

Se as expressões de argumento forem longas, use novas linhas e recuo em um nível, em vez de recuar para o parêntese esquerdo.

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

As mesmas regras se aplicam mesmo que haja apenas um único argumento de várias linhas, incluindo cadeias de caracteres de várias linhas.

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

Formatação de expressões de pipeline

Ao usar várias linhas, os operadores de pipeline |> devem ficar abaixo das expressões que operam.

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

Para operadores de pipeline <| reverso, mantenha expressões curtas em uma única linha. Quando o comprimento da linha exigir encapsulamento, coloque argumentos em novas linhas e alinhe-os consistentemente:

// ✔️ OK - short expressions stay on one line
let result = someFunction <| arg1 <| arg2 <| arg3

// ✔️ OK - longer expressions can wrap when necessary
failwith
<| sprintf "A very long error message that exceeds reasonable line length: %s - additional details: %s"
    longVariableName
    anotherLongVariableName

// ✔️ OK - align continuation lines with the operator
let longResult =
    someVeryLongFunctionName
    <| firstVeryLongArgumentName
    <| secondVeryLongArgumentName
    <| thirdVeryLongArgumentName

// ❌ Not OK - unnecessary wrapping of short expressions
failwith <| sprintf "short: %s"
                    value

Formatação de expressões lambda

Quando uma expressão lambda é usada como um argumento em uma expressão de multilinhas e é seguida por outros argumentos, coloque o corpo de uma expressão lambda em uma nova linha, recuada por um nível:

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

Se o argumento lambda for o último argumento em um aplicativo de funções, coloque todos os argumentos até a seta na mesma linha.

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

Trate a expressão lambda correspondente de forma semelhante.

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

Quando há muitos argumentos principais ou multilinhas antes do lambda, indente todos os argumentos com um nível.

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

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

Se o corpo de uma expressão lambda tiver várias linhas de comprimento, considere a refatoração em uma função com escopo local.

Quando os pipelines incluem expressões lambda, cada expressão lambda normalmente é o último argumento em cada fase do pipeline:

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

Se os argumentos de um lambda não couberem em uma única linha ou forem compostos por várias linhas, coloque-os na linha seguinte, indentados por um nível.

// ✔️ 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 reliance on whitespace alignment that is contingent to length of an identifier.
let useAddEntry () =
    fun (input: {| name: string
                   amount: Amount
                   isIncome: bool
                   created: string |}) ->
        // foo
        bar ()

Formatação de expressões lentas

Ao escrever expressões lentas de linha única, mantenha tudo em uma linha:

// ✔️ OK
let x = lazy (computeValue())

// ✔️ OK  
let y = lazy (a + b)

Para expressões preguiçosas de várias linhas, coloque o parêntese de abertura na mesma linha da palavra-chave lazy, com o corpo da expressão recuado em um nível, e o parêntese de fechamento alinhado com a abertura.

// ✔️ OK
let v =
    lazy (
        // some code
        let x = computeExpensiveValue()
        let y = computeAnotherValue()
        x + y
    )

// ✔️ OK
let handler =
    lazy (
        let connection = openConnection()
        let data = fetchData connection
        processData data
    )

Isso segue o mesmo padrão que outros aplicativos de função com argumentos multilinha. O parêntese de abertura permanece com lazy, e a expressão é indentada em um nível.

Formatação de expressões aritméticas e binárias

Sempre use espaço em branco em torno de expressões aritméticas binárias:

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

A falha em cercar um operador binário -, quando combinado com determinadas opções de formatação, pode levar à sua interpretação como um unário -. Os operadores unários - sempre devem ser seguidos imediatamente pelo valor negado:

// ✔️ OK
let negate x = -x

// ❌ Not OK
let negateBad x = - x

A adição de um caractere de espaço em branco após o - operador pode causar confusão para outras pessoas.

Separe os operadores binários por espaços. As expressões de infixo são adequadas para alinhamento na mesma coluna.

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

// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
    arg1 + arg2 +
    arg3 + arg4

Essa regra também se aplica a unidades de medidas em tipos e anotações constantes:

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

Os operadores a seguir são definidos na biblioteca padrão F# e devem ser usados em vez de definir equivalentes. O uso desses operadores é recomendado, pois ele tende a tornar o código mais legível e idiomático. A lista a seguir resume os operadores F# recomendados.

// ✔️ OK
x |> f // Forward pipeline
f <| x // Reverse 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

Formatação de expressões para operadores de intervalo

Adicione espaços ao redor do .. apenas se qualquer expressão não for atômica. Números inteiros e identificadores de palavra única são considerados atômicos.

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

Essas regras também se aplicam ao fatiamento:

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

Formatação de expressões condicionais

O recuo das condicionais depende do tamanho e da complexidade das expressões que as compõem. Escreva-os em uma linha quando:

  • cond, e1e e2 são curtos.
  • e1 e e2 não são expressões por si mesmas if/then/else.
// ✔️ OK
if cond then e1 else e2

Se a expressão 'else' estiver ausente, é recomendável nunca escrever a expressão inteira em uma linha. Isso é para diferenciar o código imperativo do funcional.

// ✔️ OK
if a then
    ()

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

Se qualquer uma das expressões for em várias linhas, cada ramo condicional deverá ser em várias linhas.

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

Várias condicionais com elif e else são recuadas no mesmo escopo que if quando seguem as regras das expressões de linha única if/then/else.

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

Se qualquer uma das condições ou expressões for multilinha, toda a expressão if/then/else será multilinha:

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

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

Se uma condição for multilinha ou exceder a tolerância padrão da linha única, a expressão de condição deverá usar um recuo e uma nova linha. A palavra-chave if e then devem estar alinhadas ao encapsular a expressão de condição longa.

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

No entanto, é um estilo melhor para refatorar condições longas para uma função de associação ou de separação de let:

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

if performAction then
    e1
else
    e2

Formatação de expressões de casos de união

A aplicação de casos de união discriminados segue as mesmas regras que os aplicativos de função e método. Ou seja, como o nome é colocado em maiúscula, os formatadores de código removerão um espaço antes de uma tupla:

// ✔️ OK
let opt = Some("A", 1)

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

Assim como as aplicações de funções, as construções que se dividem entre múltiplas linhas devem usar indentação.

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

Formatação de expressões de lista e matriz

Escreva x :: l com espaços ao redor do operador :: (:: é um operador infixo, portanto, rodeado por espaços).

A lista e as matrizes declaradas em uma única linha devem ter um espaço após o colchete de abertura e antes do colchete de fechamento:

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

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

Sempre use pelo menos um espaço entre dois operadores distintos do tipo chave. Por exemplo, deixe um espaço entre um [ e um {.

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

A mesma diretriz se aplica a listas ou matrizes de tuplas.

Listas e matrizes que se dividem em várias linhas seguem uma regra semelhante à dos registros:

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

Assim como acontece com os registros, declarar os colchetes de abertura e fechamento em linhas separadas facilita a movimentação de código e encaminhamento em funções.

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

Se uma expressão de lista ou array estiver no lado direito de uma associação, talvez você prefira usar o estilo Stroustrup:

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

No entanto, quando uma expressão de lista ou matriz não está à direita de uma associação, como quando está dentro de outra lista ou matriz, se essa expressão interna precisar abranger várias linhas, os colchetes devem estar em linhas separadas.

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

// ❌ Not okay
let fn a b = [ [
    someReallyLongValueThatWouldForceThisListToSpanMultipleLines
    a
]; [
    b
    someReallyLongValueThatWouldForceThisListToSpanMultipleLines
] ]

A mesma regra se aplica a tipos de registro dentro de matrizes/listas:

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

// ❌ Not okay
let fn a b = [ {
    Foo = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
    Bar = a
}; {
    Foo = b
    Bar = someReallyLongValueThatWouldForceThisListToSpanMultipleLines
} ]

Ao gerar matrizes e listas programaticamente, prefira -> do que do ... yield quando um valor é sempre gerado:

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

Versões mais antigas do F# exigiam especificação yield em situações em que os dados podem ser gerados condicionalmente ou pode haver expressões consecutivas a serem avaliadas. Prefira omitir essas palavras-chave yield, a menos que você precise compilar uma versão mais antiga da linguagem 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"
    ]

Em alguns casos, do...yield pode ajudar na legibilidade. Esses casos, embora subjetivos, devem ser levados em consideração.

Formatação de expressões de registro

Registros curtos podem ser gravados em uma linha:

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

Os registros mais longos devem usar novas linhas para rótulos:

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

Estilos de formatação de colchetes multilinha

Para registros que abrangem várias linhas, há três estilos de formatação comumente usados: Cramped, Aligned e Stroustrup. O estilo Cramped tem sido o padrão para o código F#, pois tende a favorecer estilos que permitem que o compilador analise facilmente o código. Os estilos Aligned e Stroustrup permitem a reordenação mais fácil de membros, levando a um código que pode ser mais fácil de refatorar, com a desvantagem de que determinadas situações podem exigir um código um pouco mais detalhado.

  • Cramped: o padrão histórico e o formato de registro F# padrão. Os colchetes de abertura ficam na mesma linha que o primeiro membro, e os de fechamento na mesma linha que o último membro.

    let rainbow = 
        { Boss1 = "Jeffrey"
          Boss2 = "Jeffrey"
          Boss3 = "Jeffrey"
          Lackeys = [ "Zippy"; "George"; "Bungle" ] }
    
  • Aligned: cada um dos colchetes obtém a própria linha, alinhada na mesma coluna.

    let rainbow =
        {
            Boss1 = "Jeffrey"
            Boss2 = "Jeffrey"
            Boss3 = "Jeffrey"
            Lackeys = ["Zippy"; "George"; "Bungle"]
        }
    
  • Stroustrup: O colchete de abertura fica na mesma linha que a vinculação, e o colchete de fechamento fica em sua própria linha.

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

As mesmas regras de estilo de formatação se aplicam a elementos de lista e matriz.

Formatação Copiar e Atualizar expressões de registro

Uma expressão de registro de copiar e atualizar ainda é um registro, portanto, aplicam-se diretrizes semelhantes.

Expressões curtas podem caber em uma linha:

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

Expressões mais longas devem usar novas linhas e formatar com base em uma das convenções nomeadas acima:

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

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

Observação: se estiver usando o estilo Stroustrup para expressões de cópia e atualização, você precisará recuar membros além do nome do registro copiado:

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

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

Formatação de padrões correspondentes

Use um | para cada cláusula de um match sem indentação. Se a expressão for curta, você poderá considerar o uso de uma única linha se cada subexpressão também for simples.

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

A formatação de correspondência de padrões deve ser consistente com a formatação de expressão. Não adicione um espaço antes do parêntese de abertura nos argumentos de parâmetros:

// ✔️ OK
match x with
| Some(y) -> y
| None -> 0

// ✔️ OK
match data with
| Success(value) -> value
| Error(msg) -> failwith msg

// ❌ Not OK, pattern formatting should match expression formatting
match x with
| Some (y) -> y
| None -> 0

No entanto, use espaços entre argumentos separados em padrões, assim como nas expressões:

// ✔️ OK - space between curried arguments
match x with
| Pattern arg (a, b) -> processValues arg a b

// ❌ Not OK - missing space between curried arguments
match x with
| Pattern arg(a, b) -> processValues arg a b

Se a expressão à direita da seta de correspondência de padrão for muito grande, mova-a para a linha seguinte, recuada um passo a partir de match/|.

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

Semelhante às condições grandes, se uma expressão de correspondência for multilinha ou exceder a tolerância padrão da linha única, a expressão de correspondência deverá usar um recuo e uma nova linha. As palavras-chave match e with devem estar alinhadas ao encapsular a expressão de correspondência longa.

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

No entanto, é um estilo melhor para refatorar expressões de correspondência longa para uma função de associação ou de separação de let:

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

match performAction with
| X y -> y
| _ -> 0

O alinhamento das setas de uma correspondência padrão deve ser evitado.

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

Os padrões correspondentes introduzidos por uma palavra-chave function devem recuar um nível desde o início da linha anterior:

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

O uso de function nas funções definidas por let ou let rec deve ser evitado em geral em favor de um match. Se usadas, as regras de padrão devem estar alinhadas à palavra-chave 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

Formatação de expressões try/with

Os padrões correspondentes no tipo de exceção devem ser recuados no mesmo nível que 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"

Adicione uma | para cada cláusula, exceto quando houver apenas uma única cláusula:

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

Formatação de argumentos nomeados

Os argumentos nomeados devem ter espaços próximo de =:

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

Ao corresponder padrões usando uniões discriminadas, os padrões nomeados são formatados de maneira semelhante, por exemplo.

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

Formatação de expressões de mutação

As expressões de mutação location <- expr normalmente são formatadas em uma linha. Se a formatação de multilinhas for necessária, coloque a expressão do lado direito em uma nova linha.

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

Formatação de expressões de objeto

Os membros da expressão de objeto devem ser alinhados com member, sendo recuados por um nível.

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

Você também pode preferir usar o estilo Stroustrup:

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

Definições de tipo vazias podem ser formatadas em uma linha:

type AnEmptyType = class end

Independentemente da largura da página escolhida, = class end deve estar sempre na mesma linha.

Formatação de expressões de índice/segmento

As expressões de índice não devem conter espaços ao redor dos colchetes de abertura e fechamento.

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

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

Isso também se aplica à sintaxe expr.[idx] mais antiga.

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

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

Formatação de expressões entre aspas

Os símbolos delimitadores (<@, @>, <@@, @@>) devem ser colocados em linhas separadas se a expressão entre aspas for uma expressão de várias linhas.

// ✔️ OK
<@
    let f x = x + 10
    f 20
@>

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

Em expressões de linha única, os símbolos delimitadores devem ser colocados na mesma linha que a própria expressão.

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

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

Formatação de expressões encadeadas

Quando as expressões encadeadas (aplicativos de função entrelaçados com .) forem longas, coloque cada invocação de aplicativo na próxima linha. Indente os links subsequentes na cadeia de links por um nível após o link principal.

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

// ✔️ OK
Cli
    .Wrap("git")
    .WithArguments(arguments)
    .WithWorkingDirectory(__SOURCE_DIRECTORY__)
    .ExecuteBufferedAsync()
    .Task

O link principal pode ser composto de vários links se forem identificadores simples. Por exemplo, a adição de um namespace totalmente qualificado.

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

Os links subsequentes também devem conter identificadores simples.

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

Quando os argumentos dentro de um aplicativo de funções não se ajustarem ao restante da linha, coloque cada argumento na próxima linha.

// ✔️ OK
WebHostBuilder()
    .UseKestrel()
    .UseUrls("http://*:5000/")
    .UseCustomCode(
        longArgumentOne,
        longArgumentTwo,
        longArgumentThree,
        longArgumentFour
    )
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseStartup<Startup>()
    .Build()

// ✔️ OK
Cache.providedTypes
    .GetOrAdd(cacheKey, addCache)
    .Value

// ❌ Not OK, formatting tools will reformat to the above
Cache
    .providedTypes
    .GetOrAdd(
        cacheKey,
        addCache
    )
    .Value

Os argumentos lambda dentro de um aplicativo de funções devem começar na mesma linha que o ( de abertura.

// ✔️ OK
builder
    .WithEnvironment()
    .WithLogger(fun loggerConfiguration ->
        // ...
        ())

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

Formatação de declarações

Esta seção discute a formatação de declarações de diferentes tipos.

Adicionar linhas em branco entre declarações

Separe as definições de função e classe de nível superior com uma única linha em branco. Por exemplo:

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

Se um constructo tiver comentários da documentação XML, adicione uma linha em branco antes do comentário.

// ✔️ OK

/// This is a function
let thisFunction() =
    1 + 1

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

Formatação de declarações de let e membro

Ao formatar declarações let e member, normalmente, o lado direito de uma vinculação fica em uma linha ou (se for muito longo) vai para uma nova linha com um nível de indentação adicional.

Por exemplo, os seguintes exemplos são compatíveis:

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

Estes não são compatíveis:

// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""

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

As instanciações de tipo de registro também podem colocar os colchetes nas suas próprias linhas:

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

Você também pode preferir usar o estilo Stroustrup, com o { de abertura na mesma linha que o nome da associação:

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

Separe membros com uma única linha em branco e um documento e adicione um comentário de documentação:

// ✔️ OK

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

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

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

Linhas em branco extras podem ser usadas (com moderação) para separar grupos de funções relacionadas. Linhas em branco podem ser omitidas entre várias declarações simples relacionadas (por exemplo, um conjunto de implementações fictícias). Use linhas em branco em funções, com moderação, para indicar seções lógicas.

Formatação de argumentos de função e membro

Ao definir uma função, use o espaço em branco em torno de cada argumento.

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

Se você tiver uma definição de função longa, coloque os parâmetros em novas linhas e recue-os para corresponder ao nível de recuo do parâmetro subsequente.

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

Isso também se aplica a membros, construtores e parâmetros que utilizam tuplas:

// ✔️ 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 TypeWithLongSecondaryConstructor () =
    new
        (
            aVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
            aSecondVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse,
            aThirdVeryLongCtorParam: AVeryLongTypeThatYouNeedToUse
        ) =
        // ... the body of the constructor follows

Se os parâmetros forem curried, coloque o caractere = junto com qualquer tipo de retorno em uma nova linha:

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

Esta é uma forma de evitar linhas muito longas (no caso de o tipo de retorno ter um nome longo) e causar menor dano às linhas ao adicionar parâmetros.

Formatação de declarações do operador

Em alternativa, use espaço em branco para cercar uma definição de operador:

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

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

Para qualquer operador personalizado que comece com * e tenha mais de um caractere, você precisa adicionar um espaço em branco ao início da definição para evitar uma ambiguidade do compilador. Por isso, recomendamos que você simplesmente envolva as definições de todos os operadores com um único caractere de espaço em branco.

Formatação de declarações de registro

Para declarações de registro, por padrão, você deve indentar o { na definição de tipo em quatro espaços, iniciar a lista de rótulos na mesma linha e alinhar membros, se houver, com o token {:

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

Também é comum preferir colocar colchetes em uma linha separada, com rótulos recuados por quatro espaços adicionais.

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

Você também pode colocar o { no final da primeira linha da definição de tipo (estilo Stroustrup):

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

Se membros adicionais forem necessários, não use with/end sempre que possível:

// ✔️ OK
type PostalAddress =
    { Address: string
      City: string
      Zip: string }
    member x.ZipAndCity = $"{x.Zip} {x.City}"

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

A exceção a essa regra de estilo será se você formatar registros de acordo com o estilo Stroustrup. Nessa situação, devido às regras do compilador, a palavra-chave with será necessária se você quiser implementar uma interface ou adicionar membros adicionais:

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

Quando a documentação XML é adicionada para campos de registro, o estilo Aligned ou Stroustrup é preferencial, e um espaço em branco adicional deve ser adicionado entre os membros:

// ❌ Not OK - putting { and comments on the same line should be avoided
type PostalAddress =
    { /// The address
      Address: string

      /// The city
      City: string

      /// The zip code
      Zip: string }

    /// Format the zip code and the city
    member x.ZipAndCity = $"{x.Zip} {x.City}"

// ✔️ OK
type PostalAddress =
    {
        /// The address
        Address: string

        /// The city
        City: string

        /// The zip code
        Zip: string
    }

    /// Format the zip code and the city
    member x.ZipAndCity = $"{x.Zip} {x.City}"

// ✔️ OK - Stroustrup Style
type PostalAddress = {
    /// The address
    Address: string

    /// The city
    City: string

    /// The zip code
    Zip: string
} with
    /// Format the zip code and the city
    member x.ZipAndCity = $"{x.Zip} {x.City}"

É recomendável colocar o token de abertura em uma nova linha e o token de fechamento em uma nova linha se você declarar implementações de interface ou membros no registro:

// ✔️ OK
// Declaring additional members on PostalAddress
type PostalAddress =
    {
        /// The address
        Address: string

        /// The city
        City: string

        /// The zip code
        Zip: string
    }

    member x.ZipAndCity = $"{x.Zip} {x.City}"

// ✔️ OK
type MyRecord =
    {
        /// The record field
        SomeField: int
    }
    interface IMyInterface

Essas mesmas regras se aplicam a aliases de tipo de registro anônimo.

Formatação de declarações de união discriminadas

Para declarações de união discriminadas, indente | na definição de tipo por quatro espaços.

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

Quando há uma única união curta, você pode omitir o elemento inicial |.

// ✔️ OK
type Address = Address of string

Para uma união mais longa ou de várias linhas, mantenha o | e coloque cada campo de união em uma nova linha, com o * separador no final de cada linha.

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

Quando os comentários da documentação forem adicionados, use uma linha vazia antes de cada comentário ///.

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

Formatação de declarações literais

Os literais F# que utilizam o atributo Literal devem colocar o atributo em uma linha própria e usar a nomenclatura em PascalCase:

// ✔️ OK

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

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

Evite colocar o atributo na mesma linha que o valor.

Formatação de declarações de módulo

O código em um módulo local deve ser indentado em relação a ele, mas o código em um módulo de nível superior não deve ser indentado. Os elementos de namespace não precisam ser recuados.

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

Formatação de declarações

Em declarações de tipo, declarações de módulo e expressões de computação, o uso de do ou do!, às vezes, é necessário para operações com efeito colateral. Quando elas abrangem múltiplas linhas, use o recuo e uma nova linha para manter o recuo consistente com let/let!. Veja um exemplo usando do em uma classe:

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

Veja aqui um exemplo com do! usando dois espaços de recuo (porque, do! coincidentemente, não há diferença entre as abordagens ao usar quatro espaços de recuo):

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

Formatação de operações de expressão de computação

Ao criar operações personalizadas para expressões de computação, é recomendável usar a nomenclatura 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
    }

O domínio que está sendo modelado deve, em última análise, orientar a convenção de nomenclatura. Se for idiomático usar uma convenção diferente, essa convenção deverá ser usada em seu lugar.

Se o valor retornado de uma expressão for uma expressão de computação, prefira colocar o nome da palavra-chave da expressão de computação em sua própria linha.

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

Você também pode preferir colocar a expressão de computação na mesma linha que o nome da associação:

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

Independentemente de sua preferência, você deve ter como objetivo permanecer consistente em toda a base de código. Os formatadores podem permitir que você especifique essa preferência para permanecer consistente.

Tipos de formatação e anotações de tipo

Esta seção discute tipos de formatação e anotações de tipo. Isso inclui a formatação de arquivos de assinatura com a extensão .fsi.

Para tipos, prefira a sintaxe de prefixo para genéricos (Foo<T>), com algumas exceções específicas

O F# permite o estilo de sufixo ao escrever tipos genéricos (por exemplo, int list) e o estilo de prefixo (por exemplo, list<int>). O estilo de sufixo somente pode ser usado com um argumento de tipo único. Sempre prefira o estilo .NET, exceto para seis tipos específicos:

  1. Para listas F#, use o formulário de sufixo: int list em vez de list<int>.
  2. Para opções F#, use o formulário de sufixo: int option em vez de option<int>.
  3. Para opções de valor F#, use o formulário de sufixo: int voption em vez de voption<int>.
  4. Para matrizes F#, use o formulário de sufixo: int array em vez de array<int> ou int[].
  5. Para células de referência, use int ref ao invés de ref<int> ou Ref<int>.
  6. Para sequências F#, use o formulário de postfixo: int seq em vez de seq<int>.

Para todos os outros tipos, use a forma de prefixo.

Formatação de tipos de função

Ao definir a assinatura de uma função, use o espaço em branco ao redor do símbolo ->:

// ✔️ OK
type MyFun = int -> int -> string

// ❌ Not OK
type MyFunBad = int->int->string

Anotações de valor de formatação e de tipo de argumento

Ao definir valores ou argumentos com anotações de tipo, use espaço em branco após o símbolo :, mas não antes:

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

Formatação de anotações de tipo em múltiplas linhas

Quando uma anotação de tipo for longa ou multilinha, coloque-as na próxima linha, recuada por um nível.

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

Para tipos de registro anônimo embutidos, você também pode usar o estilo Stroustrup:

let f
    (x: {|
        x: int
        y: AReallyLongTypeThatIsMuchLongerThan40Characters
     |})
    =
    x

Formatação de anotações de tipo de retorno

Nas anotações de tipo de retorno de função ou membro, use espaço em branco antes e depois do símbolo ::

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

Formatação de tipos de assinatura

Às vezes, ao escrever tipos de função completos em assinaturas, é necessário dividir os argumentos em várias linhas. O tipo de retorno é sempre indentado.

Para uma função tuplada, os argumentos são separados por * e colocados no final de cada linha.

Por exemplo, considere uma função com a seguinte implementação:

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

No arquivo de assinatura correspondente (extensão .fsi), a função pode ser formatada da seguinte maneira quando é necessária a formatação de multilinhas:

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

Da mesma forma, considere uma função curried:

let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...

No arquivo de assinatura correspondente, os -> são colocados no final de cada linha:

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

Da mesma forma, considere uma função que usa uma combinação de argumentos curried e tupled:

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

No arquivo de assinatura correspondente, os tipos precedidos por uma tupla são destacados.

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

As mesmas regras se aplicam a membros em assinaturas de tipo:

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

Formatação de restrições e argumentos genéricos explícitos

As diretrizes abaixo se aplicam a definições de função, definições de membro, definições de tipo e aplicativos de função.

Mantenha argumentos de tipo genérico e restrições em uma única linha se não forem muito longos:

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

Se os argumentos/restrições de tipo genéricos e parâmetros de função não se ajustarem, mas os parâmetros/restrições de tipo por si só se ajustarem, coloque esses parâmetros em novas linhas, no layout.

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

Se os parâmetros ou restrições de tipo forem muito longos, quebre e alinhe-os conforme mostrado abaixo. Mantenha a lista de parâmetros de tipo na mesma linha que a função, independentemente de seu comprimento. Para restrições, coloque when na primeira linha e mantenha cada restrição em uma única linha, independentemente de seu comprimento. Coloque > no final da última linha. Indente as restrições em um nível.

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

Se os parâmetros/restrições de tipo forem divididos, mas não houver parâmetros de função normais, coloque = em uma nova linha, independentemente:

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

As mesmas regras se aplicam a aplicativos de funções:

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

Herança de formatação

Os argumentos para o construtor de classe base aparecem na lista de argumentos na cláusula inherit. Coloque a cláusula inherit em uma nova linha, recuada por um nível.

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)

Quando um constructo for longo ou multilinha, coloque-os na próxima linha, recuada por um nível.
Formate esse constructo multilinha de acordo com as regras de aplicativos de funções multilinha.

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
        """)

Formatando o construtor primário

Em convenções de formatação padrão, nenhum espaço é adicionado entre o nome do tipo e os parênteses do construtor primário.

// ✔️ OK
type MyClass() =
    class
    end

type MyClassWithParams(x: int, y: int) =
    class
    end
        
// ❌ Not OK
type MyClass () =
    class
    end

type MyClassWithParams (x: int, y: int) =
    class
    end

Vários construtores

Quando a cláusula inherit fizer parte de um registro, coloque-a na mesma linha se for curta. E coloque-a na próxima linha, recuada por um nível, se for longa ou multilinha.

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 }

Formatação de atributos

Os atributos são colocados acima de um constructo:

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

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

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

Eles devem procurar por qualquer documentação XML:

// ✔️ OK

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

Formatação de atributos nos parâmetros

Os atributos também podem ser colocados em parâmetros. Nesse caso, coloque então na mesma linha que o parâmetro e antes do nome:

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

Formatação de vários atributos

Quando vários atributos são aplicados a um constructo que não é um parâmetro, coloque cada atributo em uma linha separada:

// ✔️ OK

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

Quando aplicado a um parâmetro, coloque os atributos na mesma linha e separe-os com um separador ;.

Agradecimentos

Essas diretrizes são baseadas em um guia abrangente das Convenções de Formatação F# por Anh-Dung Phan.