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

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

  • Mais legível
  • De acordo com as convenções aplicadas pelas ferramentas de formatação no Visual Studio Code e outros editores
  • Semelhante a outro código on-line

Consulte também Convenções de codificação e Diretrizes de design de componentes, que também abrange 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.

Recomendamos vivamente a utilização deste formatador de código. Dentro das equipes de F#, as especificações de formatação de código devem ser acordadas e codificadas em termos de um arquivo de configurações acordado para o formatador de código verificado no repositório da equipe.

Regras gerais de formatação

F# usa espaço em branco significativo por padrão e é sensível a espaços em branco. As orientações que se seguem destinam-se a fornecer orientações sobre a forma de conciliar alguns desafios que tal pode impor.

Utilizar espaços e não separadores

Quando o recuo é necessário, você deve usar espaços, não guias. O código F# não usa guias, e o compilador dará um erro se um caractere de tabulação for encontrado fora de um literal ou comentário de cadeia de caracteres.

Usar recuo consistente

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

Recomendamos quatro espaços por recuo.

Dito isto, o recuo dos programas é uma questão subjetiva. As variações são OK, mas a primeira regra que você deve seguir é a consistência do recuo. Escolha um estilo de recuo geralmente aceito e use-o sistematicamente em toda a sua base de código.

Evite formatações sensíveis ao comprimento do nome

Procure evitar recuos e alinhamentos 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 _ -> ()
    | ...

As principais razões para evitar isso são:

  • Código importante é movido para a direita
  • Há menos largura restante para o código real
  • Renomear pode quebrar o alinhamento

Evite espaços em branco estranhos

Evite espaços em branco estranhos no código F#, exceto quando descrito neste guia de estilos.

// ✔️ OK
spam (ham 1)

// ❌ Not OK
spam ( ham 1 )

Formatação de comentários

Prefira vários comentários com barra dupla em vez de bloquear comentários.

// 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 colocar a primeira letra em maiúsculas e ser frases ou 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 de documentos XML, consulte "Formatando declarações" abaixo.

Formatação de expressões

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

Formatando expressões de cadeia de caracteres

Literais de cadeia de caracteres e cadeias de caracteres interpoladas podem ser deixadas em uma única linha, independentemente de quanto tempo a linha é.

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

Expressões interpoladas com várias linhas são desencorajadas. Em vez disso, vincule o resultado da expressão a um valor e use-o na cadeia de caracteres interpolada.

Formatação de expressões de tupla

Uma instanciação de tupla deve ser parêntese, e as vírgulas delimitadoras 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 na correspondência de padrões 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 de retorno 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 bom evitar parênteses.

Formatando expressões de aplicativo

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

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

Omita parênteses, a menos que os argumentos os 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 omita espaços ao invocar com vários argumentos 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)

Nas convenções de formatação padrão, um espaço é adicionado ao aplicar funções minúsculas a argumentos interpolados 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)

Nas convenções de formatação padrão, nenhum espaço é adicionado ao aplicar métodos em maiúsculas a argumentos tupled. Isso ocorre porque estes são frequentemente usados com 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)

Talvez seja necessário passar argumentos para uma função em uma nova linha por uma questão de legibilidade ou porque a lista de argumentos ou os nomes dos argumentos são muito longos. Nesse caso, recue 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 usa um único argumento tupled de várias linhas, 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 recue 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 se houver 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" ]
)

Formatando expressões de pipeline

Ao usar várias linhas, os operadores de oleodutos |> devem passar por baixo das expressões em 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

Formatação de expressões lambda

Quando uma expressão lambda é usada como argumento em uma expressão de várias linhas 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ção, 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 lambda de fósforo de forma semelhante.

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

Quando houver muitos argumentos à esquerda ou de várias linhas antes do recuo lambda, 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, você deve considerar 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 estágio 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}")

Caso os argumentos de uma lambda não caibam em uma única linha, ou sejam multilinhas, coloque-os na próxima linha, recuada 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 aritméticas e binárias

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

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

Deixar de cercar um operador binário - , quando combinado com certas opções de formatação, pode levar a interpretá-lo como um unário -. Os operadores unários - devem ser sempre imediatamente seguidos pelo valor que negam:

// ✔️ OK
let negate x = -x

// ❌ Not OK
let negateBad x = - x

Adicionar um caractere de espaço em branco após o - operador pode causar confusão para os outros.

Separe os operadores binários por espaços. As expressões Infix são OK para alinhar na mesma coluna:

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

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

Esta regra também se aplica às unidades de medida 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 do F# e devem ser usados em vez de definir equivalentes. O uso desses operadores é recomendado, pois 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 >> 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

Formatando expressões do operador de intervalo

Adicione espaços ao redor do .. quando todas as expressões não forem atômicas. Números inteiros e identificadores de uma única palavra 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 ]

Estas regras também se aplicam ao fatiamento:

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

Formatação se expressões

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

  • cond, e1e e2 são curtos.
  • e1 e e2 não if/then/else são expressões em si.
// ✔️ 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. Isto é 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 multi-linha, cada ramificação condicional deve ser multi-linha.

// ✔️ 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 quando if seguem as regras das expressões de uma linha 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 multi-linha, toda a if/then/else expressão será multi-linha:

// ✔️ 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 da condição deverá usar um recuo e uma nova linha. A if palavra-chave e then deve ser alinhada 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, melhor estilo refatorar condições longas para uma função let binding ou separada:

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

if performAction then
    e1
else
    e2

Formatando expressões de maiúsculas e minúsculas da

A aplicação de casos sindicais discriminados segue as mesmas regras que as aplicações de funções e métodos. Ou seja, como o nome está em maiúsculas, o code formatters removerá 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)

Como as aplicações de função, as construções que se dividem em várias linhas devem usar recuo:

// ✔️ 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, cercado :: por espaços).

Lista e 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; |]

Use sempre pelo menos um espaço entre dois operadores distintos semelhantes a uma cinta. Por exemplo, deixe um espaço entre a [ e a {.

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

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

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

As 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 |] |]

Tal como acontece com os registos, declarar os parênteses de abertura e fecho na sua própria linha facilitará a movimentação do código e a canalização para 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 matriz for o lado direito de uma ligação, você pode preferir usar Stroustrup estilo:

// ✔️ 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 é o lado direito de uma ligação, como quando está dentro de outra lista ou matriz, se essa expressão interna precisa abranger várias linhas, os colchetes devem ir em suas próprias linhas:

// ✔️ 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 aos 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 -> quando do ... yield 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 yield palavras-chave, a menos que você precise compilar com uma versão mais antiga do idioma 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. Estes casos, embora subjetivos, devem ser levados em consideração.

Formatando expressões de registro

Os registos curtos podem ser escritos numa linha:

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

Registros mais longos devem usar novas linhas para rótulos:

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

Estilos de formatação de colchete de várias linhas

Para registros que abrangem várias linhas, há três estilos de formatação comumente usados: Cramped, Alignede Stroustrup. O Cramped estilo tem sido o estilo padrão para o código F#, pois tende a favorecer estilos que permitem que o compilador analise facilmente o código. Ambos os Aligned estilos permitem Stroustrup uma reordenação mais fácil dos membros, levando a um código que pode ser mais fácil de refatorar, com a desvantagem de que certas 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 vão na mesma linha do primeiro membro, fechando o colchete na mesma linha do último membro.

    let rainbow = 
        { Boss1 = "Jeffrey"
          Boss2 = "Jeffrey"
          Boss3 = "Jeffrey"
          Lackeys = [ "Zippy"; "George"; "Bungle" ] }
    
  • Aligned: Cada colchete tem sua própria linha, alinhada na mesma coluna.

    let rainbow =
        {
            Boss1 = "Jeffrey"
            Boss2 = "Jeffrey"
            Boss3 = "Jeffrey"
            Lackeys = ["Zippy"; "George"; "Bungle"]
        }
    
  • Stroustrup: O colchete de abertura vai na mesma linha que a ligação, o colchete de fechamento obtém 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 aos elementos de lista e matriz.

Formatando expressões de registro de cópia e atualização

Uma expressão de registro de cópia e atualização ainda é um registro, portanto, diretrizes semelhantes se aplicam.

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

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

Nota: Se estiver usando Stroustrup estilo para copiar e atualizar expressões, você deverá recuar membros além do nome de 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" 
}

Correspondência de padrões de formatação

Use a | para cada cláusula de uma correspondência sem recuo. Se a expressão for curta, você pode 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"

Se a expressão à direita da seta de correspondência de padrão for muito grande, mova-a para a linha a seguir, recuada a uma etapa do 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 if 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. A match palavra-chave e with deve ser alinhada 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, melhor estilo refatorar expressões de correspondência longa para uma função let binding ou separada:

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

A correspondência de padrões introduzida usando a palavra-chave function deve recuar um nível a partir do 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 em funções definidas por let ou let rec deve, em geral, ser evitado em favor de um match. Se usadas, as regras de padrão devem ser alinhadas com a 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

Tentativa de formatação/com expressões

A correspondência de padrões no tipo de exceção deve ser recuada 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 um | 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

Formatar argumentos nomeados

Os argumentos nomeados devem ter espaços ao redor do =:

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

Quando a correspondência de padrões usa uniões discriminadas, os padrões nomeados são formatados de forma 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

Formatando expressões de mutação

As expressões location <- expr de mutação são normalmente formatadas em uma linha. Se a formatação de várias linhas 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

Formatando expressões de objeto

Os membros da expressão de objeto devem estar alinhados com member o recuo de 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 Stroustrup estilo:

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

As 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.

Formatar expressões de índice/fatia

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 mais antiga expr.[idx] .

// ✔️ 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. Recue os elos subsequentes da cadeia por um nível após o elo 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ção não se encaixarem no resto 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 do Lambda dentro de um aplicativo de função devem começar na mesma linha da 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 uma construção tiver comentários de documento 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

Formatando declarações de permissão e membro

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

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 estão em conformidade:

// ❌ 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 registo também podem colocar os parênteses nas suas próprias linhas:

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

Você também pode preferir usar Stroustrup estilo, com a abertura { na mesma linha do nome da ligação:

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

Separe os membros com uma única linha em branco e 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 adicionais podem ser usadas (com moderação) para separar grupos de funções relacionadas. Linhas em branco podem ser omitidas entre um monte de one-liners relacionados (por exemplo, um conjunto de implementações fictícias). Use linhas em branco nas funções, com moderação, para indicar seções lógicas.

Função de formatação e argumentos de membro

Ao definir uma função, use espaço em branco ao redor 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 usando 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 estiverem curados, 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 maneira de evitar linhas muito longas (no caso de o tipo de retorno pode ter nome longo) e ter menos danos de linha ao adicionar parâmetros.

Formatando declarações de operador

Opcionalmente, 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 que 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 rodeie as definições de todos os operadores com um único caractere de espaço em branco.

Formatando declarações de registro

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

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

Também é comum preferir colocar parênteses em sua própria linha, com rótulos recuados por mais quatro espaços:

// ✔️ 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 (Stroustrup estilo):

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

Se forem necessários membros adicionais, 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 é se você formatar registros de acordo com o Stroustrup estilo. Nessa situação, devido às regras do compilador, a with palavra-chave é 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, Aligned ou Stroustrup o estilo é preferido, e 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}"

Colocar o token de abertura em uma nova linha e o token de fechamento em uma nova linha é preferível se você estiver declarando 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

Estas mesmas regras aplicam-se a aliases de tipo de registo anónimo.

Formatação de declarações sindicais discriminadas

Para declarações de união discriminadas, travessão | 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 a liderança |.

// ✔️ OK
type Address = Address of string

Para uma união mais longa ou com várias linhas, mantenha o | e coloque cada campo de união em uma nova linha, com a separação * 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 usam o Literal atributo devem colocar o atributo em sua própria linha e usar a nomenclatura 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.

Formatando declarações de módulo

O código em um módulo local deve ser recuado em relação ao módulo, mas o código em um módulo de nível superior não deve ser recuado. 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 de efeito colateral. Quando elas se estenderem por várias linhas, use recuo e uma nova linha para manter o recuo consistente com let/let!. Aqui está um exemplo de uso 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

Aqui está um exemplo com do! o uso de dois espaços de recuo (porque coincidentemente do! 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
}

Formatando operações de expressão computacional

Ao criar operações personalizadas para expressões de computação, recomenda-se 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, conduzir a convenção de nomenclatura. Se é idiomática usar uma convenção diferente, essa convenção deve ser usada em vez disso.

Se o valor de retorno 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 ligação:

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

Seja qual for a sua preferência, você deve procurar permanecer consistente em toda a sua base de código. Formatters pode 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 .fsi extensão.

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

F# permite tanto o estilo postfix de escrever tipos genéricos (por exemplo, int list) quanto o estilo de prefixo (por exemplo, list<int>). O estilo Postfix só pode ser usado com um único argumento de tipo. Prefira sempre o estilo .NET, exceto para cinco tipos específicos:

  1. Para listas F#, use o formulário postfix: int list em vez de list<int>.
  2. Para Opções de F#, use o formulário postfix: int option em vez de option<int>.
  3. Para Opções de Valor F#, use o formulário postfix: int voption em vez de voption<int>.
  4. Para matrizes F#, use o formulário postfix: int array em vez de array<int> ou int[].
  5. Para células de referência, use int ref em vez de ref<int> ou Ref<int>.

Para todos os outros tipos, use o formulário de prefixo.

Formatação de tipos de função

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

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

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

Formatação de valor e anotações 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

Formatando anotações de tipo de várias linhas

Quando uma anotação de tipo for longa ou multilinha, coloque-a 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ônimos embutidos, você também pode usar Stroustrup estilo:

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

Formatando anotações de tipo de retorno

Em 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

Tipos de formatação em assinaturas

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

Para uma função tupled, os argumentos são separados por *, 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 (.fsi extensão) a função pode ser formatada da seguinte forma quando a formatação de várias linhas é necessária:

// ✔️ 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 mistura de argumentos enrolados 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 recuados

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

Aplicam-se as mesmas regras aos membros em assinaturas tipográficas:

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

Formatando argumentos e restrições de tipo genérico explícito

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éricos e restrições em uma única linha se ela não for muito longa:

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

Se os argumentos/restrições de tipo genéricos e os parâmetros de função não se ajustarem, mas os parâmetros/restrições de tipo sozinhos se ajustarem, coloque os parâmetros em novas linhas:

// ✔️ 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-os e alinhe-os como mostrado abaixo. Mantenha a lista de parâmetros de tipo na mesma linha da função, independentemente do 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. Recue as restrições por 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 quebrados, mas não houver parâmetros de função normais, coloque o = em uma nova linha independentemente disso:

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

Aplicam-se as mesmas regras às aplicações 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

Formatação de herança

Os argumentos para o construtor de classe base aparecem na lista de argumentos na inherit cláusula. Coloque a cláusula em uma nova linha, recuada inherit 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 o construtor for longo ou multilinha, coloque-os na próxima linha, recuado por um nível.
Formate este construtor multiline de acordo com as regras de aplicações de função 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

Nas convenções de formatação padrão, nenhum espaço é adicionado entre o nome do tipo e os parênteses para o 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 inherit cláusula fizer parte de um registo, coloque-a na mesma linha se for curta. E coloque-o na próxima linha, recuado por um nível, se for longo 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 }

Atributos de formatação

Os atributos são colocados acima de uma construção:

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

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

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

Eles devem ir atrás de qualquer documentação XML:

// ✔️ OK

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

Atributos de formatação em parâmetros

Os atributos também podem ser colocados em parâmetros. Neste 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)

Formatando vários atributos

Quando vários atributos são aplicados a uma construção 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 atributos na mesma linha e separe-os com um ; separador.

Agradecimentos

Estas diretrizes são baseadas em Um guia abrangente para Convenções de Formatação de F# de Anh-Dung Phan.