Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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,e1ee2são curtos. -
e1ee2não são expressões por si mesmasif/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:
- Para listas F#, use o formulário de sufixo:
int listem vez delist<int>. - Para opções F#, use o formulário de sufixo:
int optionem vez deoption<int>. - Para opções de valor F#, use o formulário de sufixo:
int voptionem vez devoption<int>. - Para matrizes F#, use o formulário de sufixo:
int arrayem vez dearray<int>ouint[]. - Para células de referência, use
int refao invés deref<int>ouRef<int>. - Para sequências F#, use o formulário de postfixo:
int seqem vez deseq<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.