Diretrizes de formatação de código do F#
Este artigo oferece diretrizes para formatar seu código para que seu código F# seja:
- Mais legível
- De acordo com as convenções aplicadas por ferramentas de formatação no Visual Studio Code e outros editores
- Semelhante a outros códigos online
Consulte também as convenções de codificação e as diretrizes de design de componente, que também incluem convenções de nomenclatura.
Formatação automática de código
O formatador de código Fantomas é a ferramenta padrão da comunidade F# para formatação automática de código. As configurações padrão correspondem a este guia de estilo.
É altamente recomendável o uso desse formatador de código. Dentro das equipes do F#, as especificações de formatação de código devem ser acordadas e codificadas em termos de um arquivo de configurações acordado para o formatador de código verificado no repositório da equipe.
Regras gerais para formatação
O F# usa espaço em branco por padrão e diferencia 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 guias
Quando o recuo é necessário, use espaços e não guias. O código F# não usa guias e o compilador gera um erro se um caractere de guia for encontrado fora de um literal de cadeia de caracteres ou comentário.
Usar recuo consistente
Ao recuar, 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 recuo.
Dito isto, o recuo de programas é um assunto subjetivo. As variações acontecem, 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 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
- Há menos largura para o código real
- 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 de barra dupla 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 cadeia de caracteres e cadeias de caracteres interpoladas podem ficar em uma única linha, independentemente do tamanho da 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 tupla
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 padrões correspondentes ou um valor retornado, é bom 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 com letras maiúsculas a argumentos de tupla. Isso ocorre porque geralmente são usados na programação fluent:
// ✔️ OK - Methods accepting parenthesize arguments are applied without a space
SomeClass.Invoke()
// ✔️ OK - Methods accepting tuples are applied without a space
String.Format(x.IngredientName, x.Quantity)
// ❌ Not OK, formatting tools will remove the extra space by default
SomeClass.Invoke ()
// ❌ Not OK, formatting tools will remove the extra space by default
String.Format (x.IngredientName, x.Quantity)
Talvez seja necessário aprovar argumentos para uma função em uma nova linha como uma questão de legibilidade ou porque a lista de argumentos ou os nomes dos argumentos são muito longos. Nesse caso, recuou 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 em tupla de multilinhas, 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 argumento de multilinhas, incluindo cadeias de caracteres de multilinhas:
// ✔️ 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 estar debaixo das expressões operadas.
// ✔️ 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 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 nas entrelinhas ou multilinhas 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 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}")
Caso os argumentos de um lambda não caibam em uma única linha ou sejam multilinha, coloque-os na próxima linha, recuados 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
Sempre use espaço em branco em torno de expressões aritméticas binárias:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Falha ao cercar um operador binário -
, quando combinado com determinadas opções de formatação, pode levar a interpretá-lo 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 estão OK para a formação 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 >> 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 de operador de intervalo
Adicione apenas espaços ao redor de ..
quando todas as expressões não são atômicas.
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 à divisão:
// ✔️ OK
arr[0..10]
list[..^1]
Formatação de expressões
O recuo das condicionais depende do tamanho e da complexidade das expressões que as compõem. Escreva-os em uma linha quando:
cond
,e1
ee2
são curtos.e1
ee2
nãoif/then/else
são expressões em si.
// ✔️ OK
if cond then e1 else e2
Se a outra expressão estiver ausente, é recomendável nunca gravar 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 de várias linhas, cada branch condicional deverá ser de 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 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 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 caso 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 os aplicativos de funções, as construções que se dividem entre multilinhas 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
Gravação x :: l
com espaços ao redor do operador ::
(::
é um operador infixo, portanto, cercado 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 semelhantes a chaves. 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 entre multilinhas 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 sua própria linha facilita a movimentação de código e tubulação 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 matriz for o lado direito de uma associação, você poderá preferir 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 é o lado direito 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 deverão 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 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, fechando o colchete 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 associação, o colchete de fechamento obtém a 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, são aplicadas 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 |
para cada cláusula de uma correspondência sem recuo. 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"
Se a expressão à direita da seta de padrões correspondentes for muito grande, mova-a para a linha seguinte, recuada a uma etapa 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.
A palavra-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 try/com expressões
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)
Quando os padrões correspondentes usando uniões discriminadas, os padrões nomeados são formatados da mesma forma, 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
, tendo o recuo 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/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 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.
Recue os links subsequentes na cadeia 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 à esquerda poderá ser composto por vários links se eles 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 associação fica em uma linha ou (se for muito longo) entra em um novo nível recuado de uma linha.
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 um monte de linhas únicas 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
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 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
Essa é uma forma de evitar linhas muito longas (caso o tipo de retorno possa ter nome longo) e menos danos de linha 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 recuar 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 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 (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, faça o recuo |
na definição de tipo em 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 entrelinhamento |
.
// ✔️ OK
type Address = Address of string
Para uma união mais longa ou multilinhas, mantenha o |
e coloque cada campo de união em uma nova linha, com a separação de *
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
OsLiterais F# usando o atributo Literal
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.
Formatação de 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 com efeito colateral.
Quando elas incluem multilinhas, use o recuo e uma nova linha para manter o recuo de acordo 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 preparado deve, em última análise, conduzir a convenção de nomenclatura. Se for idioma 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 a expressão de computação na 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.
Formatação de tipos 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 de 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 cinco tipos específicos:
- Para listas F#, use o formulário de sufixo:
int list
em vez delist<int>
. - Para opções F#, use o formulário de sufixo:
int option
em vez deoption<int>
. - Para opções de valor F#, use o formulário de sufixo:
int voption
em vez devoption<int>
. - Para matrizes F#, use o formulário de sufixo:
int array
em vez dearray<int>
ouint[]
. - Para células de referência, use
int ref
ao invés deref<int>
ouRef<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 o espaço em branco ao redor do símbolo ->
:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Anotação de valor de formatação 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
Formatação de anotações de tipo de multilinha
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
Ao escrever tipos de função completos em assinaturas, às vezes, é necessário dividir os argumentos em multilinhas. 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 (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 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
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 ambos os argumentos/restrições de tipo genéricos e parâmetros de função não se ajustarem, mas se os parâmetros/restrições de tipo fizerem, 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 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. Recue 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 constructos
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 ir atrás de 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.