Riktlinjer för F#-kodformatering
Den här artikeln innehåller riktlinjer för hur du formaterar koden så att din F#-kod är:
- Mer läsbar
- I enlighet med konventioner som tillämpas av formateringsverktyg i Visual Studio Code och andra redigerare
- Liknar annan kod online
Se även kodningskonventioner och riktlinjer för komponentdesign, som även omfattar namngivningskonventioner.
Automatisk kodformatering
Fantomas-kodformateraren är standardverktyget för F#-communityn för automatisk kodformatering. Standardinställningarna motsvarar den här formatguiden.
Vi rekommenderar starkt att du använder den här kodformaterare. I F#-team ska specifikationer för kodformatering överenskommas och kodas i form av en överenskommen inställningsfil för kodformateraren som checkas in på teamlagringsplatsen.
Allmänna regler för formatering
F# använder betydande blanksteg som standard och är blankstegskänsligt. Följande riktlinjer är avsedda att ge vägledning om hur du kan jonglera vissa utmaningar som detta kan medföra.
Använd blanksteg, inte flikar
När indrag krävs måste du använda blanksteg, inte flikar. F#-kod använder inte flikar, och kompilatorn ger ett fel om ett fliktecken påträffas utanför en strängliteral eller kommentar.
Använd konsekvent indrag
Vid indrag krävs minst ett utrymme. Din organisation kan skapa kodningsstandarder för att ange hur många blanksteg som ska användas för indrag. två, tre eller fyra indragsutrymmen på varje nivå där indrag inträffar är typiskt.
Vi rekommenderar fyra blanksteg per indrag.
Med detta sagt är indrag av program en subjektiv fråga. Variationer är OK, men den första regeln som du bör följa är konsekvens för indrag. Välj ett allmänt godkänt indragsformat och använd det systematiskt i hela kodbasen.
Undvik formatering som är känslig för namnlängd
Försök att undvika indrag och justering som är känslig för namngivning:
// ✔️ 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 _ -> ()
| ...
De främsta orsakerna till att undvika detta är:
- Viktig kod flyttas långt till höger
- Det finns mindre bredd kvar för den faktiska koden
- Om du byter namn kan justeringen brytas
Undvik överflödigt tomt utrymme
Undvik överflödigt tomt utrymme i F#-kod, förutom där beskrivs i den här formatguiden.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Formatera kommentarer
Föredrar flera kommentarer med dubbla snedstreck framför blockkommentar.
// 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.
*)
Kommentarer bör versalisera den första bokstaven och vara välformulerad fraser eller meningar.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Information om hur du formaterar XML-dokumentkommenterar finns i "Formateringsdeklarationer" nedan.
Formateringsuttryck
I det här avsnittet beskrivs formateringsuttryck av olika slag.
Formatera stränguttryck
Strängliteraler och interpolerade strängar kan bara lämnas på en enda rad, oavsett hur lång linjen är.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
Interpolerade uttryck med flera linjer rekommenderas inte. Binda i stället uttrycksresultatet till ett värde och använd det i den interpolerade strängen.
Formatera tuppelns uttryck
En tuppel-instansiering ska parenteseras och avgränsande kommatecken i den ska följas av ett enda blanksteg, till exempel: (1, 2)
, (x, y, z)
.
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
Det är allmänt accepterat att utelämna parenteser i mönstermatchning av tupplar:
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
Det är också allmänt accepterat att utelämna parenteser om tuppeln är returvärdet för en funktion:
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
Sammanfattningsvis föredrar du parentesiserade tuppelns instansier, men när du använder tupplar för mönstermatchning eller ett returvärde anses det bra att undvika parenteser.
Formatera programuttryck
När du formaterar en funktion eller ett metodprogram anges argument på samma rad när radbredd tillåter:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Utelämna parenteser om inte argumenten kräver dem:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
Utelämna inte blanksteg när du anropar med flera curryargument:
// ✔️ 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)
I standardformateringskonventioner läggs ett blanksteg till när gemener tillämpas på tupled eller parentesiserade argument (även när ett enda argument används):
// ✔️ 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)
I standardformateringskonventioner läggs inget utrymme till när versaler tillämpas på tupled-argument. Detta beror på att dessa ofta används med flytande programmering:
// ✔️ 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)
Du kan behöva skicka argument till en funktion på en ny rad som en fråga om läsbarhet eller eftersom listan med argument eller argumentnamnen är för lång. I så fall kan du dra in en nivå:
// ✔️ 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)
När funktionen tar ett enda Tupled-argument med flera rader placerar du varje argument på en ny rad:
// ✔️ 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)
Om argumentuttrycken är korta, avgränsar du argumenten med blanksteg och behåller dem på en rad.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Om argumentuttrycken är långa använder du nya rader och drar in en nivå i stället för att dra in till vänsterparentesen.
// ✔️ 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)
Samma regler gäller även om det bara finns ett enda argument med flera rader, inklusive flerradssträngar:
// ✔️ 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" ]
)
Formatera pipelineuttryck
När du använder flera rader bör pipelineoperatorer |>
gå under de uttryck som de använder.
// ✔️ 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
Formatera lambda-uttryck
När ett lambda-uttryck används som ett argument i ett flerradsuttryck och följs av andra argument placerar du brödtexten för ett lambda-uttryck på en ny rad, indragen med en nivå:
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Om lambda-argumentet är det sista argumentet i ett funktionsprogram placerar du alla argument tills pilen på samma rad.
// ✔️ 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}")
Behandla match lambda's på ett liknande sätt.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
När det finns många inledande eller flerradsargument innan lambda drar in alla argument med en nivå.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Om brödtexten i ett lambda-uttryck är flera rader långt bör du överväga att omstrukturera det till en lokalt begränsad funktion.
När pipelines innehåller lambda-uttryck är varje lambda-uttryck vanligtvis det sista argumentet i varje steg i pipelinen:
// ✔️ 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}")
Om argumenten för en lambda inte får plats på en enda rad, eller själva är flera rader, placerar du dem på nästa rad, indragen med en nivå.
// ✔️ 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 ()
Formatera aritmetiska och binära uttryck
Använd alltid tomt utrymme runt binära aritmetiska uttryck:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Om du inte omger en binär -
operator i kombination med vissa formateringsalternativ kan det leda till att den tolkas som en unary -
.
Unary-operatorer -
bör alltid omedelbart följas av värdet de negerar:
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
Att lägga till ett blankstegstecken efter operatorn -
kan leda till förvirring för andra.
Avgränsa binära operatorer med blanksteg. Infixuttryck är OK för att uppställning i samma kolumn:
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Den här regeln gäller även för måttenheter i typer och konstanta anteckningar:
// ✔️ 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)> }
Följande operatorer definieras i F#-standardbiblioteket och bör användas i stället för att definiera motsvarigheter. Att använda dessa operatorer rekommenderas eftersom det tenderar att göra koden mer läsbar och idiomatisk. I följande lista sammanfattas de rekommenderade F#-operatorerna.
// ✔️ 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
Formateringsintervalloperatoruttryck
Lägg bara till blanksteg runt ..
när alla uttryck inte är atomiska.
Heltal och enstaka ordidentifierare betraktas som atomiska.
// ✔️ 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 ]
Dessa regler gäller även för segmentering:
// ✔️ OK
arr[0..10]
list[..^1]
Formatering om uttryck
Indrag av villkor beror på storleken och komplexiteten för de uttryck som utgör dem. Skriv dem på en rad när:
-
cond
,e1
oche2
är korta. -
e1
oche2
är inteif/then/else
själva uttrycken.
// ✔️ OK
if cond then e1 else e2
Om else-uttrycket saknas rekommenderar vi att du aldrig skriver hela uttrycket på en rad. Detta är för att skilja den imperativa koden från funktionen.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Om något av uttrycken är flera rader bör varje villkorsgren vara flera rader.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Flera villkor med elif
och är indragna i samma omfång som else
när de följer reglerna för enradsuttrycken if
if/then/else
.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Om något av villkoren eller uttrycken är flera rader är hela if/then/else
uttrycket flera rader:
// ✔️ 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
Om ett villkor är flera rader eller överskrider standardtoleransen för en rad, bör villkorsuttrycket använda en indrag och en ny rad.
Nyckelordet if
och then
bör justeras när det långa villkorsuttrycket kapslars in.
// ✔️ 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
Det är dock bättre format att omstrukturera långa villkor till en let-bindning eller separat funktion:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Formatera unionsfalluttryck
Tillämpning av fall av diskriminerade fackföreningar följer samma regler som funktions- och metodprogram. Eftersom namnet är versalt tar kodformatanter bort ett blanksteg före en tuppeln:
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
Precis som funktionsprogram bör konstruktioner som delas upp över flera linjer använda indrag:
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Formateringslista och matrisuttryck
Skriv x :: l
med blanksteg runt operatorn ::
(::
är en infixoperator, därför omgiven av blanksteg).
Lista och matriser som deklareras på en enda rad bör ha ett blanksteg efter den inledande hakparentesen och före den avslutande hakparentesen:
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Använd alltid minst ett blanksteg mellan två distinkta klammerparentesliknande operatorer. Lämna till exempel ett blanksteg mellan en [
och en {
.
// ✔️ 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 }]
Samma riktlinje gäller för listor eller matriser med tupplar.
Listor och matriser som delas över flera rader följer en liknande regel som poster gör:
// ✔️ 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 |] |]
Precis som med poster blir det enklare att deklarera inledande och avslutande hakparenteser på sin egen rad, vilket gör det enklare att flytta runt koden och skicka den till funktioner:
// ✔️ 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 |]
|]
Om en lista eller ett matrisuttryck är den högra sidan av en bindning kanske du föredrar att använda Stroustrup
formatmall:
// ✔️ 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 |]
|]
Men om ett list- eller matrisuttryck inte är höger sida av en bindning, till exempel när det finns i en annan lista eller matris, bör hakparenteserna gå på sina egna linjer om det inre uttrycket behöver sträcka sig över flera rader:
// ✔️ 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
] ]
Samma regel gäller för posttyper i matriser/listor:
// ✔️ 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
} ]
När du genererar matriser och listor programmatiskt föredrar du ->
framför do ... yield
när ett värde alltid genereras:
// ✔️ 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 ]
Äldre versioner av F# krävs för att yield
ange i situationer där data kan genereras villkorligt, eller så kan det finnas på varandra följande uttryck som ska utvärderas. Föredrar att utelämna dessa yield
nyckelord om du inte måste kompilera med en äldre F#-språkversion:
// ✔️ 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"
]
I vissa fall do...yield
kan underlätta läsbarheten. Dessa fall, även om de är subjektiva, bör beaktas.
Formatera postuttryck
Korta poster kan skrivas på en rad:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
Poster som är längre bör använda nya rader för etiketter:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Formatmallar för flerradsparentes
För poster som sträcker sig över flera rader finns det tre formatmallar som ofta används: Cramped
, Aligned
och Stroustrup
. Formatet Cramped
har varit standardformatet för F#-kod, eftersom det tenderar att gynna formatmallar som gör det möjligt för kompilatorn att enkelt parsa kod. Både Aligned
format och Stroustrup
format möjliggör enklare omordning av medlemmar, vilket leder till kod som kan vara lättare att omstrukturera, med nackdelen att vissa situationer kan kräva lite mer utförlig kod.
Cramped
: Den historiska standarden och standardformatet för F#-post. Inledande hakparenteser går på samma rad som den första medlemmen och stänger hakparentesen på samma rad som den sista medlemmen.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aligned
: Hakparenteser får varsin rad, justerad på samma kolumn.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }
Stroustrup
: Inledande hakparentes går på samma linje som bindningen, avslutande hakparentes får sin egen linje.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Samma formateringsformatregler gäller för list- och matriselement.
Formatera postuttryck för kopiering och uppdatering
Ett postuttryck för kopiering och uppdatering är fortfarande en post, så liknande riktlinjer gäller.
Korta uttryck får plats på en rad:
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
Längre uttryck bör använda nya rader och format baserat på någon av de ovan namngivna konventionerna:
// ✔️ 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 = ""
}
}
Obs! Om du använder Stroustrup
formatmall för kopierings- och uppdateringsuttryck måste du dra in medlemmar längre än det kopierade postnamnet:
// ✔️ 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"
}
Formateringsmönstermatchning
Använd en |
för varje sats i en matchning utan indrag. Om uttrycket är kort kan du överväga att använda en enda rad om varje underuttryck också är enkelt.
// ✔️ 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"
Om uttrycket till höger om mönstermatchningspilen är för stort flyttar du det till följande rad, indraget ett steg från match
/|
.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Om ett matchningsuttryck är flera rader eller överskrider standardtoleransen för en rad, bör matchningsuttrycket använda en indrag och en ny rad.
Nyckelordet match
och with
bör justeras när det långa matchningsuttrycket kapslar in.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
Det är dock bättre format att omstrukturera långa matchningsuttryck till en let-bindning eller separat funktion:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
Du bör undvika att justera pilarna i en mönstermatchning.
// ✔️ 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
Mönstermatchning som introduceras med hjälp av nyckelordet function
bör dra in en nivå från början av föregående rad:
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
Användning av function
i funktioner som definieras av let
eller let rec
bör i allmänhet undvikas till förmån för en match
. Om det används bör mönsterreglerna överensstämma med nyckelordet 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
Formatera try/with expressions
Mönstermatchning för undantagstypen ska vara indraget på samma nivå som 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"
Lägg till en |
för varje sats, förutom när det bara finns en enda sats:
// ✔️ 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
Formatering med namn på argument
Namngivna argument bör ha blanksteg som =
omger :
// ✔️ 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)
När mönstermatchning med hjälp av diskriminerade fackföreningar formateras namngivna mönster på liknande sätt, till exempel.
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
Formatera mutationsuttryck
Mutationsuttryck formateras location <- expr
normalt på en rad.
Om formatering med flera rader krävs placerar du uttrycket till höger på en ny rad.
// ✔️ 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
Formatera objektuttryck
Objektuttrycksmedlemmar bör justeras med member
indrag på en nivå.
// ✔️ 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) }
Du kanske också föredrar att använda Stroustrup
formatmall:
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)
}
Tomma typdefinitioner kan formateras på en rad:
type AnEmptyType = class end
Oavsett den valda sidbredden = class end
bör alltid finnas på samma rad.
Formatera index/segmentuttryck
Indexuttryck får inte innehålla blanksteg runt de inledande och avslutande hakparenteserna.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
Detta gäller även för den äldre expr.[idx]
syntaxen.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Formatera citerade uttryck
Avgränsarsymbolerna (<@
, @>
, <@@
, @@>
) ska placeras på separata rader om det citerade uttrycket är ett flerradsuttryck.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
I enradsuttryck ska avgränsarsymbolerna placeras på samma rad som själva uttrycket.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Formatera länkade uttryck
När länkade uttryck (funktionsprogram som är sammanflätade med .
) är långa placerar du varje programanrop på nästa rad.
Dra in efterföljande länkar i kedjan med en nivå efter den inledande länken.
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
Den inledande länken kan bestå av flera länkar om de är enkla identifierare. Till exempel tillägg av ett fullständigt kvalificerat namnområde.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
Efterföljande länkar bör också innehålla enkla identifierare.
// ✔️ 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))
När argumenten i ett funktionsprogram inte får plats på resten av raden placerar du varje argument på nästa rad.
// ✔️ 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
Lambda-argument i ett funktionsprogram bör starta på samma rad som öppningen (
.
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Formateringsdeklarationer
I det här avsnittet beskrivs formateringsdeklarationer av olika slag.
Lägga till tomma rader mellan deklarationer
Avgränsa funktions- och klassdefinitioner på den översta nivån med en enda tom rad. Till exempel:
// ✔️ 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
Om en konstruktion har XML-dokumentkommentarer lägger du till en tom rad före kommentaren.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Formatering av let- och member-deklarationer
Vid formatering let
och member
deklarationer går vanligtvis den högra sidan av en bindning antingen på en rad eller (om den är för lång) på en ny rad med indrag på en nivå.
Följande exempel är till exempel kompatibla:
// ✔️ 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
Dessa är inkompatibla:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
Instansieringar av posttyp kan också placera hakparenteserna på sina egna linjer:
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Du kanske också föredrar att använda Stroustrup
formatmall, med öppningen {
på samma rad som bindningsnamnet:
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Avgränsa medlemmar med en enda tom rad och dokument och lägg till en dokumentationskommentar:
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
Extra tomma rader kan användas (sparsamt) för att avgränsa grupper med relaterade funktioner. Tomma rader kan utelämnas mellan ett gäng relaterade one-liners (till exempel en uppsättning dummy-implementeringar). Använd tomma rader i funktioner, sparsamt, för att ange logiska avsnitt.
Formateringsfunktion och medlemsargument
När du definierar en funktion använder du blanksteg runt varje argument.
// ✔️ 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
Om du har en lång funktionsdefinition placerar du parametrarna på nya rader och drar in dem för att matcha indragsnivån för den efterföljande parametern.
// ✔️ 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
Detta gäller även för medlemmar, konstruktorer och parametrar som använder tupplar:
// ✔️ 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
Om parametrarna är härdade placerar du =
tecknet tillsammans med en returtyp på en ny rad:
// ✔️ 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
Det här är ett sätt att undvika för långa rader (om returtypen kan ha ett långt namn) och få mindre radskador när du lägger till parametrar.
Formateringsoperatordeklarationer
Du kan också använda tomt utrymme för att omge en operatordefinition:
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
För alla anpassade operatorer som börjar med *
och som har fler än ett tecken måste du lägga till ett tomt utrymme i början av definitionen för att undvika tvetydigheter i kompilatorn. Därför rekommenderar vi att du bara omger definitionerna för alla operatorer med ett enda blankstegstecken.
Formatera postdeklarationer
För postdeklarationer bör du som standard dra in {
i typdefinitionen med fyra blanksteg, starta etikettlistan på samma rad och justera eventuella medlemmar med {
token:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
Det är också vanligt att föredra att sätta hakparenteser på sin egen linje, med etiketter indragna av ytterligare fyra blanksteg:
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
Du kan också placera {
i slutet av den första raden i typdefinitionen (Stroustrup
format):
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Om ytterligare medlemmar behövs ska du inte använda with
/end
när det är möjligt:
// ✔️ 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
Undantaget för den här formatmallsregeln är om du formaterar poster enligt Stroustrup
formatet. I det här fallet krävs nyckelordet with
på grund av kompilatorregler om du vill implementera ett gränssnitt eller lägga till ytterligare medlemmar:
// ✔️ 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}"
När XML-dokumentation läggs till för postfält, Aligned
eller Stroustrup
formatmall föredras, och ytterligare blanksteg ska läggas till mellan medlemmar:
// ❌ 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}"
Att placera öppningstoken på en ny rad och den avslutande token på en ny rad är att föredra om du deklarerar gränssnittsimplementeringar eller medlemmar i posten:
// ✔️ 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
Samma regler gäller för anonyma posttypalias.
Formatera diskriminerade fackliga deklarationer
För diskriminerade fackliga deklarationer, indrag |
i typdefinition med fyra blanksteg:
// ✔️ 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
När det finns en enda kort union kan du utelämna den ledande |
.
// ✔️ OK
type Address = Address of string
För en längre eller flerradsunion ska du behålla |
och placera varje unionfält på en ny rad, med avgränsaren *
i slutet av varje rad.
// ✔️ 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
När du lägger till dokumentationskommentarer använder du en tom rad före varje ///
kommentar.
// ✔️ 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
Formatera literaldeklarationer
F#-literaler med attributet Literal
ska placera attributet på sin egen rad och använda PascalCase-namngivning:
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Undvik att placera attributet på samma rad som värdet.
Formatering av moduldeklarationer
Kod i en lokal modul måste vara indraget i förhållande till modulen, men koden i en modul på den översta nivån ska inte vara indragen. Namnområdeselement behöver inte vara indragna.
// ✔️ 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
Formatering gör deklarationer
I typdeklarationer, moduldeklarationer och beräkningsuttryck krävs ibland användning av do
eller do!
för sidoeffektåtgärder.
När dessa sträcker sig över flera rader använder du indrag och en ny rad för att hålla indraget konsekvent med let
/let!
. Här är ett exempel som använder do
i en klass:
// ✔️ 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
Här är ett exempel på do!
hur du använder två indragsutrymmen (eftersom do!
det av en tillfällighet inte finns någon skillnad mellan metoderna när du använder fyra indragsutrymmen):
// ✔️ 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
}
Formatera beräkningsuttrycksåtgärder
När du skapar anpassade åtgärder för beräkningsuttryck rekommenderar vi att du använder camelCase-namngivning:
// ✔️ 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
}
Domänen som modelleras bör i slutändan driva namngivningskonventionen. Om det är idiomatiskt att använda en annan konvention bör den konventionen användas i stället.
Om returvärdet för ett uttryck är ett beräkningsuttryck föredrar du att placera nyckelordsnamnet för beräkningsuttrycket på sin egen rad:
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Du kanske också föredrar att placera beräkningsuttrycket på samma rad som bindningsnamnet:
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Oavsett vad du föredrar bör du sträva efter att förbli konsekvent i hela din kodbas. Formatters kan göra att du kan ange att den här inställningen ska förbli konsekvent.
Formateringstyper och skriv anteckningar
I det här avsnittet beskrivs formateringstyper och skrivanteckningar. Detta inkluderar formatering av signaturfiler med .fsi
tillägget.
För typer föredrar du prefixsyntax för generiska objekt (Foo<T>
), med vissa specifika undantag
F# tillåter både postfixstil för att skriva generiska typer (till exempel int list
) och prefixstilen (till exempel list<int>
).
Postfix-formatmallen kan bara användas med ett argument av en enda typ.
Föredrar alltid .NET-formatmallen, förutom sex specifika typer:
- För F#-listor använder du postfixformuläret:
int list
i ställetlist<int>
för . - För F#-alternativ använder du postfixformuläret:
int option
i ställetoption<int>
för . - För F#-värdealternativ använder du postfixformuläret:
int voption
i ställetvoption<int>
för . - För F#-matriser använder du postfixformuläret:
int array
i stället förarray<int>
ellerint[]
. - För referensceller använder du
int ref
i stället förref<int>
ellerRef<int>
. - För F#-sekvenser använder du postfixformuläret:
int seq
i stället förseq<int>
.
Använd prefixformuläret för alla andra typer.
Formateringsfunktionstyper
När du definierar signaturen för en funktion använder du blanksteg runt symbolen ->
:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Formateringsvärde och argumenttypanteckningar
När du definierar värden eller argument med typanteckningar använder du blanksteg efter symbolen :
, men inte före:
// ✔️ 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
Formatera anteckningar av flerradstyp
När en typanteckning är lång eller flera rader placerar du dem på nästa rad, indragen med en nivå.
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
För interna anonyma posttyper kan du också använda Stroustrup
formatmall:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Formatering av returtypsanteckningar
Använd blanksteg före och efter symbolen :
i kommentarer för funktions- eller medlemsreturtyp:
// ✔️ 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
Formateringstyper i signaturer
När du skriver fullständiga funktionstyper i signaturer är det ibland nödvändigt att dela argumenten över flera rader. Returtypen är alltid indragen.
För en tupled-funktion avgränsas argumenten med *
, placerade i slutet av varje rad.
Tänk dig till exempel en funktion med följande implementering:
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
I motsvarande signaturfil (.fsi
tillägg) kan funktionen formateras på följande sätt när formatering med flera rader krävs:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
Överväg på samma sätt en curryfunktion:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
I motsvarande signaturfil ->
placeras de i slutet av varje rad:
// ✔️ OK
val SampleCurriedFunction:
arg1: string ->
arg2: string ->
arg3: int ->
arg4: int ->
int list
På samma sätt bör du överväga en funktion som tar en blandning av curry- och tupled-argument:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
I motsvarande signaturfil är de typer som föregås av en tuppeln indragna
// ✔️ 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
Samma regler gäller för medlemmar i typsignaturer:
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Formatera explicita allmänna typargument och begränsningar
Riktlinjerna nedan gäller för funktionsdefinitioner, medlemsdefinitioner, typdefinitioner och funktionsprogram.
Behåll allmänna typargument och begränsningar på en enda rad om den inte är för lång:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Om både allmänna typargument/begränsningar och funktionsparametrar inte passar, men enbart typparametrarna/begränsningarna gör det, placerar du parametrarna på nya rader:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
param
=
// function body
Om typparametrarna eller begränsningarna är för långa kan du bryta och justera dem enligt nedan. Behåll listan med typparametrar på samma rad som funktionen, oavsett dess längd. För begränsningar placerar du when
på den första raden och behåller varje begränsning på en enskild rad oavsett längd. Placera >
i slutet av den sista raden. Dra in begränsningarna på en nivå.
// ✔️ 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
Om typparametrarna/begränsningarna är uppdelade, men det inte finns några normala funktionsparametrar, placerar du på =
en ny rad oavsett:
// ✔️ 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
Samma regler gäller för funktionsprogram:
// ✔️ 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
Formatera arv
Argumenten för basklasskonstruktorn visas i argumentlistan i inherit
-satsen.
inherit
Placera satsen på en ny rad, indragen med en nivå.
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)
När konstruktorn är lång eller flera rader placerar du dem på nästa rad, indraget på en nivå.
Formatera den här flerradskonstruktorn enligt reglerna för program med flera funktioner.
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
""")
Formatera den primära konstruktorn
I standardformateringskonventionerna läggs inget utrymme till mellan typnamnet och parenteserna för den primära konstruktorn.
// ✔️ 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
Flera konstruktorer
inherit
När satsen är en del av en post placerar du den på samma rad om den är kort.
Och placera den på nästa rad, indragen med en nivå, om den är lång eller flera rader.
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 }
Formateringsattribut
Attribut placeras ovanför en konstruktion:
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
De bör gå efter all XML-dokumentation:
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Formateringsattribut för parametrar
Attribut kan också placeras på parametrar. I det här fallet placerar du sedan på samma rad som parametern och före namnet:
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Formatera flera attribut
När flera attribut tillämpas på en konstruktion som inte är en parameter placerar du varje attribut på en separat rad:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
När du använder en parameter placerar du attribut på samma rad och separerar dem med en ;
avgränsare.
Tack
Dessa riktlinjer baseras på en omfattande guide till F#-formateringskonventioner av Anh-Dung Phan.