Linee guida per la formattazione del codice F#
Questo articolo offre linee guida per la formattazione del codice in modo che il codice F# sia:
- Più leggibile
- In base alle convenzioni applicate tramite gli strumenti di formattazione in Visual Studio Code e altri editor
- Simile ad altro codice online
Vedere anche Convenzioni di codifica e Linee guida per la progettazione dei componenti, che illustrano anche le convenzioni di denominazione.
Formattazione automatica del codice
Il formattatore di codice Fantomas è lo strumento standard della community F# per la formattazione automatica del codice. Le impostazioni predefinite corrispondono a questa guida di stile.
È consigliabile usare questo formattatore di codice. All'interno dei team F# le specifiche di formattazione del codice devono essere concordate e codificate in termini di un file di impostazioni concordato per il formattatore di codice archiviato nel repository del team.
Regole generali per la formattazione
F# usa spazi vuoti significativi per impostazione predefinita ed è sensibile agli spazi vuoti. Le linee guida seguenti sono concepite per fornire indicazioni su come destreggiarsi su alcune sfide che possono imporre.
Usare spazi non schede
Quando è necessario il rientro, è necessario usare spazi, non schede. Il codice F# non usa schede e il compilatore genererà un errore se viene rilevato un carattere di tabulazione all'esterno di un valore letterale stringa o di un commento.
Usare un rientro coerente
Quando si rientra, è necessario almeno uno spazio. L'organizzazione può creare standard di codifica per specificare il numero di spazi da usare per il rientro; due, tre o quattro spazi di rientro a ogni livello in cui si verifica il rientro è tipico.
È consigliabile usare quattro spazi per rientro.
Detto questo, il rientro dei programmi è una questione soggettiva. Le varianti sono OK, ma la prima regola da seguire è la coerenza del rientro. Scegliere uno stile di rientro generalmente accettato e usarlo sistematicamente in tutta la codebase.
Evitare la formattazione sensibile alla lunghezza del nome
Cercare di evitare il rientro e l'allineamento sensibili alla denominazione:
// ✔️ 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 _ -> ()
| ...
I motivi principali per evitare questo problema sono:
- Il codice importante viene spostato a destra
- È stata lasciata una larghezza inferiore per il codice effettivo
- La ridenominazione può interrompere l'allineamento
Evitare spazi vuoti estranei
Evitare spazi vuoti estranei nel codice F#, tranne dove descritto in questa guida di stile.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Formattazione dei commenti
Preferisce più commenti a barra doppia rispetto ai commenti di blocco.
// 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.
*)
I commenti devono maiuscolare la prima lettera e essere frasi o frasi ben formate.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Per la formattazione dei commenti della documentazione XML, vedere "Dichiarazioni di formattazione" di seguito.
Formattazione di espressioni
In questa sezione vengono illustrate le espressioni di formattazione di tipi diversi.
Formattazione di espressioni stringa
I valori letterali stringa e le stringhe interpolate possono essere lasciati su una singola riga, indipendentemente dalla durata della riga.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
Le espressioni interpolate a più righe sono sconsigliate. Associare invece il risultato dell'espressione a un valore e usarlo nella stringa interpolata.
Formattazione delle espressioni di tupla
Una creazione di un'istanza di tupla deve essere racchiusa tra parentesi e le virgole delimitatori all'interno devono essere seguite da un singolo spazio, ad esempio : (1, 2)
, (x, y, z)
.
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
È comunemente accettato di omettere parentesi nei criteri di ricerca delle tuple:
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
È anche comunemente accettato di omettere parentesi se la tupla è il valore restituito di una funzione:
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
In sintesi, preferisce le istanze delle tuple racchiuse tra parentesi, ma quando si usano tuple per la corrispondenza dei criteri o un valore restituito, è considerato corretto evitare parentesi.
Formattazione delle espressioni dell'applicazione
Quando si formatta un'applicazione di funzione o metodo, gli argomenti vengono forniti nella stessa riga quando la larghezza della riga consente:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Omettere le parentesi a meno che gli argomenti non richiedano:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
Non omettere gli spazi quando si richiamano con più argomenti 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)
Nelle convenzioni di formattazione predefinite, viene aggiunto uno spazio quando si applicano funzioni con lettere minuscole agli argomenti tupled o racchiusi tra parentesi (anche quando viene usato un singolo argomento):
// ✔️ 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)
Nelle convenzioni di formattazione predefinite non viene aggiunto alcuno spazio quando si applicano metodi in maiuscolo agli argomenti tupled. Ciò è dovuto al fatto che vengono spesso usati con la programmazione 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)
Potrebbe essere necessario passare argomenti a una funzione su una nuova riga come una questione di leggibilità o perché l'elenco di argomenti o i nomi degli argomenti sono troppo lunghi. In tal caso, impostare un rientro di un livello:
// ✔️ 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 la funzione accetta un singolo argomento tupled su più righe, posizionare ogni argomento su una nuova riga:
// ✔️ 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 le espressioni di argomento sono brevi, separare gli argomenti con spazi e mantenerli in una riga.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Se le espressioni di argomento sono lunghe, usare le righe nuove e impostare il rientro di un livello, anziché rientrare tra parentesi a sinistra.
// ✔️ 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)
Le stesse regole si applicano anche se è presente un solo argomento su più righe, incluse le stringhe a più righe:
// ✔️ 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" ]
)
Formattazione delle espressioni della pipeline
Quando si usano più righe, gli operatori della pipeline |>
devono entrare sotto le espressioni su cui operano.
// ✔️ 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
Formattazione di espressioni lambda
Quando un'espressione lambda viene usata come argomento in un'espressione a più righe e seguita da altri argomenti, posizionare il corpo di un'espressione lambda su una nuova riga, rientrato in base a un livello:
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Se l'argomento lambda è l'ultimo argomento in un'applicazione di funzione, posizionare tutti gli argomenti fino alla freccia sulla stessa riga.
// ✔️ 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}")
Trattare le corrispondenze lambda in modo simile.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Quando sono presenti molti argomenti iniziali o su più righe prima del rientro lambda di tutti gli argomenti con un livello.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Se il corpo di un'espressione lambda è lungo più righe, è consigliabile effettuare il refactoring in una funzione con ambito locale.
Quando le pipeline includono espressioni lambda, ogni espressione lambda è in genere l'ultimo argomento in ogni fase della 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}")
Nel caso in cui gli argomenti di un'espressione lambda non si adattino a una singola riga o siano su più righe, inserirli nella riga successiva, rientrati di un livello.
// ✔️ 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 ()
Formattazione di espressioni aritmetiche ed binarie
Usare sempre spazi vuoti intorno alle espressioni aritmetiche binarie:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
L'errore di racchiudere un operatore binario -
, se combinato con determinate scelte di formattazione, potrebbe comportare l'interpretazione di un operatore unario -
.
Gli operatori unari -
devono essere sempre seguiti immediatamente dal valore che negano:
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
L'aggiunta di uno spazio vuoto dopo l'operatore -
può causare confusione per altri utenti.
Separare gli operatori binari per spazi. Le espressioni di prefisso sono OK per la riga nella stessa colonna:
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Questa regola si applica anche alle unità di misura in tipi e annotazioni costanti:
// ✔️ 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)> }
Gli operatori seguenti sono definiti nella libreria standard F# e devono essere usati invece di definire equivalenti. L'uso di questi operatori è consigliato perché tende a rendere il codice più leggibile e idiomatico. L'elenco seguente riepiloga gli operatori F# consigliati.
// ✔️ 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
Formattazione delle espressioni dell'operatore di intervallo
Aggiungere spazi intorno a ..
quando tutte le espressioni non sono atomiche.
I numeri interi e gli identificatori di parola singola sono considerati atomici.
// ✔️ 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 ]
Queste regole si applicano anche al sezionamento:
// ✔️ OK
arr[0..10]
list[..^1]
Formattazione di espressioni if
Il rientro delle condizionali dipende dalle dimensioni e dalla complessità delle espressioni che li compongono. Scriverli su una riga quando:
cond
,e1
ee2
sono brevi.e1
ee2
nonif/then/else
sono espressioni stesse.
// ✔️ OK
if cond then e1 else e2
Se l'espressione else è assente, è consigliabile non scrivere mai l'intera espressione in una riga. Si tratta di distinguere il codice imperativo dalla funzionalità.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Se una qualsiasi delle espressioni è multilinea, ogni ramo condizionale deve essere multilinea.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Più condizionali con elif
e else
sono rientrati nello stesso ambito di if
quando seguono le regole delle espressioni di una riga if/then/else
.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Se una delle condizioni o delle espressioni è multilinea, l'intera if/then/else
espressione è su più righe:
// ✔️ 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 una condizione è multilinea o supera la tolleranza predefinita della riga singola, l'espressione della condizione deve usare un rientro e una nuova riga.
La if
parola chiave e then
deve essere allineata durante l'incapsulamento dell'espressione di condizione long.
// ✔️ 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
È tuttavia preferibile eseguire il refactoring di condizioni lunghe per un'associazione di tipo let o una funzione separata:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Formattazione delle espressioni di maiuscole e minuscole dell'unione
L'applicazione di casi di unione discriminati segue le stesse regole delle applicazioni di funzioni e metodi. Ovvero, poiché il nome è in maiuscolo, i formattatori di codice rimuoveranno uno spazio prima di una tupla:
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
Analogamente alle applicazioni per le funzioni, le costruzioni suddivise tra più righe devono usare il rientro:
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Formattazione di espressioni di elenco ed array
Scrivere x :: l
con spazi intorno all'operatore ::
(::
è un operatore di prefisso, quindi racchiuso da spazi).
L'elenco e le matrici dichiarate su una singola riga devono avere uno spazio dopo la parentesi aperta e prima della parentesi chiusa:
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Usare sempre almeno uno spazio tra due operatori distinti simili a parentesi graffe. Ad esempio, lasciare uno spazio tra un [
oggetto e un oggetto {
.
// ✔️ 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 }]
Le stesse linee guida si applicano agli elenchi o alle matrici di tuple.
Gli elenchi e le matrici che si dividono tra più righe seguono una regola simile a quella dei record:
// ✔️ 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 |] |]
Come per i record, dichiarando le parentesi di apertura e chiusura sulla propria riga, il codice di spostamento e il piping nelle funzioni semplificano:
// ✔️ 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 un'espressione di elenco o matrice è il lato destro di un'associazione, è consigliabile usare Stroustrup
lo stile:
// ✔️ 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 |]
|]
Tuttavia, quando un elenco o un'espressione di matrice non è il lato destro di un'associazione, ad esempio quando si trova all'interno di un altro elenco o matrice, se tale espressione interna deve estendersi su più righe, le parentesi quadre devono andare sulle proprie righe:
// ✔️ 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
] ]
La stessa regola si applica ai tipi di record all'interno di matrici/elenchi:
// ✔️ 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
} ]
Quando si generano matrici ed elenchi a livello di codice, preferire ->
quando do ... yield
viene sempre generato un valore:
// ✔️ 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 ]
Nelle versioni precedenti di F# è necessario specificare yield
in situazioni in cui i dati possono essere generati in modo condizionale oppure possono essere valutate espressioni consecutive. Preferire omettere queste yield
parole chiave a meno che non sia necessario compilare con una versione precedente del linguaggio 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"
]
In alcuni casi, do...yield
può essere utile per la leggibilità. Questi casi, anche se soggettivi, devono essere presi in considerazione.
Formattazione delle espressioni di record
I record brevi possono essere scritti in una sola riga:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
I record più lunghi devono usare nuove righe per le etichette:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Stili di formattazione tra parentesi quadre su più righe
Per i record che si estendono su più righe, esistono tre stili di formattazione comunemente usati: Cramped
, Aligned
e Stroustrup
. Lo Cramped
stile è stato lo stile predefinito per il codice F#, poiché tende a favorire gli stili che consentono al compilatore di analizzare facilmente il codice. Entrambi Aligned
gli stili e Stroustrup
consentono di riordinare più facilmente i membri, portando a codice che potrebbe essere più facile da effettuare il refactoring, con lo svantaggio che alcune situazioni possono richiedere codice leggermente più dettagliato.
Cramped
: formato di record F# cronologico e predefinito. Le parentesi quadre di apertura vanno sulla stessa riga del primo membro, con parentesi quadre chiuse sulla stessa riga dell'ultimo membro.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aligned
: le parentesi quadre ottengono la propria linea, allineate sulla stessa colonna.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }
Stroustrup
: la parentesi quadra aperta va sulla stessa riga dell'associazione, la parentesi chiusa ottiene la propria riga.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Le stesse regole di stile di formattazione si applicano per gli elementi elenco e matrice.
Formattazione delle espressioni di record di copia e aggiornamento
Un'espressione di record di copia e aggiornamento è ancora un record, quindi si applicano linee guida simili.
Le espressioni brevi possono essere adattate a una sola riga:
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
Le espressioni più lunghe devono usare nuove righe e formattare in base a una delle convenzioni indicate in precedenza:
// ✔️ OK - Cramped
let newState =
{ state with
Foo =
Some
{ F1 = 0
F2 = "" } }
// ✔️ OK - Aligned
let newState =
{
state with
Foo =
Some
{
F1 = 0
F2 = ""
}
}
// ✔️ OK - Stroustrup
let newState = {
state with
Foo =
Some {
F1 = 0
F2 = ""
}
}
Nota: se si usa Stroustrup
lo stile per le espressioni di copia e aggiornamento, è necessario impostare un rientro dei membri oltre il nome del record copiato:
// ✔️ 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"
}
Formattazione dei criteri di ricerca
Utilizzare per |
ogni clausola di una corrispondenza senza rientro. Se l'espressione è breve, è possibile prendere in considerazione l'uso di una singola riga se ogni sottoespressione è anche semplice.
// ✔️ 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 l'espressione a destra della freccia corrispondente al criterio è troppo grande, spostarla nella riga seguente, rientrando un passaggio dall'oggetto match
/|
.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Analogamente alle condizioni di grandi dimensioni, se un'espressione di corrispondenza è multilinea o supera la tolleranza predefinita della riga singola, l'espressione di corrispondenza deve usare un rientro e una nuova riga.
La match
parola chiave e with
deve essere allineata durante l'incapsulamento dell'espressione di corrispondenza lunga.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
È tuttavia preferibile eseguire il refactoring di espressioni di corrispondenza lunghe a una funzione let binding o separata:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
È consigliabile evitare l'allineamento delle frecce di una corrispondenza del criterio.
// ✔️ 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
I criteri di ricerca introdotti usando la parola chiave function
devono rientrare un livello dall'inizio della riga precedente:
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
L'uso di function
nelle funzioni definite da let
o let rec
deve essere in generale evitato a favore di un oggetto match
. Se usato, le regole del criterio devono essere allineate con la parola chiave 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
Formattazione di espressioni try/with
La corrispondenza dei criteri nel tipo di eccezione deve essere rientrata allo stesso livello di 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"
Aggiungere un |
per ogni clausola, tranne quando è presente una sola clausola:
// ✔️ 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
Formattazione di argomenti denominati
Gli argomenti denominati devono avere spazi che circondano :=
// ✔️ 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 i criteri di ricerca usano unioni discriminate, i modelli denominati vengono formattati in modo analogo, ad esempio.
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
Formattazione delle espressioni di mutazione
Le espressioni di mutazione vengono in genere formattate location <- expr
su una riga.
Se è necessaria la formattazione su più righe, posizionare l'espressione sul lato destro su una nuova riga.
// ✔️ 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
Formattazione delle espressioni di oggetto
I membri dell'espressione oggetto devono essere allineati al member
rientro di un livello.
// ✔️ 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) }
È anche consigliabile usare Stroustrup
lo stile:
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)
}
Le definizioni dei tipi vuoti possono essere formattate in una sola riga:
type AnEmptyType = class end
Indipendentemente dalla larghezza della pagina scelta, = class end
deve essere sempre nella stessa riga.
Formattazione di espressioni di indice/sezione
Le espressioni di indice non devono contenere spazi intorno alle parentesi di apertura e di chiusura.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
Questo vale anche per la sintassi precedente expr.[idx]
.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Formattazione di espressioni tra virgolette
I simboli delimitatori (<@
, @>
, <@@
, @@>
) devono essere posizionati su righe separate se l'espressione tra virgolette è un'espressione a più righe.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
Nelle espressioni a riga singola i simboli del delimitatore devono essere posizionati sulla stessa riga dell'espressione stessa.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Formattazione delle espressioni concatenati
Quando le espressioni concatenati (applicazioni di funzione intrecciate con .
) sono lunghe, inserire ogni chiamata dell'applicazione alla riga successiva.
Rientrare i collegamenti successivi nella catena di un livello dopo il collegamento iniziale.
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
Il collegamento iniziale può essere composto da più collegamenti se sono identificatori semplici. Ad esempio, l'aggiunta di uno spazio dei nomi completo.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
I collegamenti successivi devono contenere anche identificatori semplici.
// ✔️ 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 gli argomenti all'interno di un'applicazione di funzione non rientrano nel resto della riga, inserire ogni argomento nella riga successiva.
// ✔️ 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
Gli argomenti lambda all'interno di un'applicazione di funzione devono iniziare nella stessa riga dell'apertura (
di .
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Dichiarazioni di formattazione
In questa sezione vengono illustrate le dichiarazioni di formattazione di tipi diversi.
Aggiungere righe vuote tra dichiarazioni
Separare le definizioni di classe e funzione di primo livello con una singola riga vuota. Ad esempio:
// ✔️ 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 un costrutto contiene commenti di documenti XML, aggiungere una riga vuota prima del commento.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Formattazione di dichiarazioni let e membro
Durante la formattazione let
e member
le dichiarazioni, in genere il lato destro di un'associazione passa su una riga o (se è troppo lungo) passa a un nuovo livello rientrato di una riga.
Ad esempio, gli esempi seguenti sono conformi:
// ✔️ 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
Questi sono non conformi:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
Le istanze dei tipi di record possono anche posizionare le parentesi quadre sulle proprie righe:
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
È anche possibile usare Stroustrup
lo stile, con l'apertura {
sulla stessa riga del nome dell'associazione:
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Separare i membri con una singola riga vuota e un documento e aggiungere un commento alla documentazione:
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
È possibile usare righe vuote aggiuntive (con moderazione) per separare gruppi di funzioni correlate. Le righe vuote possono essere omesse tra un gruppo di righe correlate ,ad esempio un set di implementazioni fittizie. Usare righe vuote nelle funzioni, con moderazione, per indicare sezioni logiche.
Formattazione di argomenti di funzione e membro
Quando si definisce una funzione, usare spazi vuoti intorno a ogni argomento.
// ✔️ 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 si dispone di una definizione di funzione lunga, posizionare i parametri sulle nuove righe e impostare il rientro in modo che corrispondano al livello di rientro del parametro successivo.
// ✔️ 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
Questo vale anche per membri, costruttori e parametri che usano tuple:
// ✔️ 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 i parametri sono curried, posizionare il =
carattere insieme a qualsiasi tipo restituito su una nuova riga:
// ✔️ 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
Questo è un modo per evitare righe troppo lunghe (nel caso in cui il tipo restituito potrebbe avere un nome lungo) e avere meno danni alla riga durante l'aggiunta di parametri.
Dichiarazioni di operatore di formattazione
Facoltativamente, usare lo spazio vuoto per racchiudere una definizione di operatore:
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Per qualsiasi operatore personalizzato che inizia con *
e con più di un carattere, è necessario aggiungere uno spazio vuoto all'inizio della definizione per evitare un'ambiguità del compilatore. Per questo motivo, è consigliabile racchiudere semplicemente le definizioni di tutti gli operatori con un singolo carattere di spazio vuoto.
Formattazione delle dichiarazioni di record
Per le dichiarazioni di record, per impostazione predefinita è consigliabile impostare come rientro nella {
definizione del tipo in quattro spazi, avviare l'elenco di etichette nella stessa riga e allineare i membri, se presenti, con il {
token:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
È anche comune preferire l'inserimento di parentesi quadre sulla propria riga, con etichette rientrate da quattro spazi aggiuntivi:
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
È anche possibile inserire l'oggetto {
alla fine della prima riga della definizione del tipo (Stroustrup
stile):
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Se sono necessari membri aggiuntivi, non usare with
/end
quando possibile:
// ✔️ 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
L'eccezione a questa regola di stile è se si formattano i record in base allo Stroustrup
stile. In questo caso, a causa delle regole del compilatore, la with
parola chiave è necessaria se si vuole implementare un'interfaccia o aggiungere altri membri:
// ✔️ 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 viene aggiunta la documentazione XML per i campi Aligned
di record o Stroustrup
lo stile è preferibile e tra i membri devono essere aggiunti spazi vuoti aggiuntivi:
// ❌ 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}"
Posizionare il token di apertura su una nuova riga e il token di chiusura su una nuova riga è preferibile se si dichiarano implementazioni o membri dell'interfaccia nel record:
// ✔️ 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
Queste stesse regole si applicano agli alias dei tipi di record anonimi.
Formattazione di dichiarazioni di unione discriminate
Per le dichiarazioni di unione discriminate, rientrare |
nella definizione del tipo in base a quattro spazi:
// ✔️ 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 è presente una singola unione breve, è possibile omettere l'oggetto iniziale |
.
// ✔️ OK
type Address = Address of string
Per un'unione più lunga o su più righe, mantenere e |
posizionare ogni campo di unione su una nuova riga, con la separazione *
alla fine di ogni riga.
// ✔️ 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 vengono aggiunti i commenti della documentazione, usare una riga vuota prima di ogni ///
commento.
// ✔️ 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
Formattazione di dichiarazioni letterali
I valori letterali F# che usano l'attributo Literal
devono inserire l'attributo nella propria riga e usare la denominazione PascalCase:
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Evitare di posizionare l'attributo sulla stessa riga del valore.
Dichiarazioni di modulo di formattazione
Il codice in un modulo locale deve essere rientrato rispetto al modulo, ma il codice in un modulo di primo livello non deve essere rientrato. Gli elementi dello spazio dei nomi non devono essere rientrati.
// ✔️ 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
Formattazione delle dichiarazioni do
Nelle dichiarazioni di tipo, le dichiarazioni di modulo e le espressioni di calcolo, l'uso di do
o è do!
talvolta necessario per le operazioni di effetto collaterale.
Quando si estendono su più righe, usare il rientro e una nuova riga per mantenere il rientro coerente con let
/let!
. Ecco un esempio che usa do
in una 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
Di seguito è riportato un esempio con do!
l'uso di due spazi di rientro (perché con do!
non esiste una differenza casuale tra gli approcci quando si usano quattro spazi di rientro):
// ✔️ 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
}
Formattazione delle operazioni di espressione di calcolo
Quando si creano operazioni personalizzate per le espressioni di calcolo, è consigliabile usare la denominazione 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
}
Il dominio modellato dovrebbe infine guidare la convenzione di denominazione. Se è idiomatico usare una convenzione diversa, è consigliabile usare tale convenzione.
Se il valore restituito di un'espressione è un'espressione di calcolo, è preferibile inserire il nome della parola chiave dell'espressione di calcolo nella propria riga:
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
È anche consigliabile inserire l'espressione di calcolo nella stessa riga del nome dell'associazione:
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Indipendentemente dalla preferenza, è consigliabile mantenere la coerenza in tutta la codebase. I formattatori possono consentire di specificare questa preferenza per rimanere coerenti.
Formattazione di tipi e annotazioni di tipo
In questa sezione vengono illustrati i tipi di formattazione e le annotazioni dei tipi. Ciò include la formattazione dei file di firma con l'estensione .fsi
.
Per i tipi, preferire la sintassi del prefisso per i generics (Foo<T>
), con alcune eccezioni specifiche
F# consente sia lo stile di scrittura di tipi generici (ad esempio, int list
) che lo stile del prefisso (ad esempio, list<int>
).
Lo stile di postfissi può essere usato solo con un singolo argomento di tipo.
Preferisce sempre lo stile .NET, ad eccezione di cinque tipi specifici:
- Per gli elenchi F# usare il formato di prefisso:
int list
anzichélist<int>
. - Per Le opzioni F# usare il formato di prefisso:
int option
anzichéoption<int>
. - Per Opzioni valore F#, usare il formato di prefisso:
int voption
anzichévoption<int>
. - Per le matrici F#, usare il formato di prefisso:
int array
anzichéarray<int>
oint[]
. - Per le celle di riferimento, usare
int ref
anzichéref<int>
oRef<int>
.
Per tutti gli altri tipi, usare il formato prefisso.
Formattazione dei tipi di funzione
Quando si definisce la firma di una funzione, usare spazi vuoti intorno al ->
simbolo:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Formattazione di annotazioni di valori e tipi di argomento
Quando si definiscono valori o argomenti con annotazioni di tipo, usare spazi vuoti dopo il :
simbolo, ma non prima:
// ✔️ 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
Formattazione di annotazioni di tipi multilinea
Quando un'annotazione di tipo è lunga o multilinea, inserirle nella riga successiva, rientrate di un livello.
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
Per i tipi di record anonimi inline, è anche possibile usare Stroustrup
lo stile:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Formattazione delle annotazioni dei tipi restituiti
Nelle annotazioni di tipo restituito di funzione o membro usare spazi vuoti prima e dopo il :
simbolo:
// ✔️ 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
Formattazione dei tipi nelle firme
Quando si scrivono tipi di funzione completi nelle firme, a volte è necessario suddividere gli argomenti su più righe. Il tipo restituito è sempre rientrato.
Per una funzione tupled, gli argomenti sono separati da *
, posizionati alla fine di ogni riga.
Si consideri ad esempio una funzione con l'implementazione seguente:
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
Nel file di firma corrispondente (.fsi
estensione) la funzione può essere formattata come segue quando è necessaria la formattazione a più righe:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
Si consideri analogamente una funzione curried:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
Nel file di firma corrispondente, l'oggetto ->
viene posizionato alla fine di ogni riga:
// ✔️ OK
val SampleCurriedFunction:
arg1: string ->
arg2: string ->
arg3: int ->
arg4: int ->
int list
Analogamente, si consideri una funzione che accetta una combinazione di argomenti curried e tupled:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
Nel file di firma corrispondente i tipi preceduti da una tupla sono rientrati
// ✔️ 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
Le stesse regole si applicano ai membri nelle firme di tipo:
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Formattazione di argomenti e vincoli di tipo generico espliciti
Le linee guida seguenti si applicano alle definizioni di funzione, alle definizioni dei membri, alle definizioni dei tipi e alle applicazioni per le funzioni.
Mantenere argomenti di tipo generico e vincoli su una singola riga se non è troppo lungo:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Se entrambi gli argomenti o i vincoli di tipo generico e i parametri di funzione non sono adatti, ma i parametri di tipo o i vincoli si applicano da soli, posizionare i parametri nelle nuove righe:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
param
=
// function body
Se i parametri di tipo o i vincoli sono troppo lunghi, interromperli e allinearli come illustrato di seguito. Mantenere l'elenco dei parametri di tipo nella stessa riga della funzione, indipendentemente dalla lunghezza. Per i vincoli, posizionare when
sulla prima riga e mantenere ogni vincolo su una singola riga indipendentemente dalla relativa lunghezza. Posizionare >
alla fine dell'ultima riga. Rientro dei vincoli di un livello.
// ✔️ 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 i parametri o i vincoli di tipo vengono suddivisi, ma non sono presenti parametri di funzione normali, posizionare su =
una nuova riga indipendentemente da quanto indicato di seguito:
// ✔️ 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
Le stesse regole si applicano alle applicazioni per le funzioni:
// ✔️ 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
Formattazione dell'ereditarietà
Gli argomenti per il costruttore della classe base vengono visualizzati nell'elenco di argomenti nella inherit
clausola .
Inserire la inherit
clausola su una nuova riga, rientrata di un livello.
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 il costruttore è lungo o multilinea, inserirli nella riga successiva, rientrati di un livello.
Formattare questo costruttore su più righe in base alle regole delle applicazioni per le funzioni multilinea.
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
""")
Formattazione del costruttore primario
Nelle convenzioni di formattazione predefinite non viene aggiunto alcuno spazio tra il nome del tipo e le parentesi per il costruttore primario.
// ✔️ 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
Più costruttori
Quando la inherit
clausola fa parte di un record, inserirla nella stessa riga se è breve.
E metterlo sulla riga successiva, rientrato da un livello, se è lungo o multilinea.
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 }
Formattazione degli attributi
Gli attributi vengono posizionati sopra un costrutto:
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
Devono andare dopo qualsiasi documentazione XML:
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Formattazione degli attributi nei parametri
Gli attributi possono anche essere inseriti nei parametri. In questo caso, posizionare quindi sulla stessa riga del parametro e prima del nome:
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Formattazione di più attributi
Quando più attributi vengono applicati a un costrutto che non è un parametro, posizionare ogni attributo su una riga separata:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Se applicato a un parametro, posizionare gli attributi sulla stessa riga e separarli con un ;
separatore.
Riconoscimenti
Queste linee guida sono basate su una guida completa alle convenzioni di formattazione F# di Anh-Dung Phan.