Pravidla formátování kódu F#
Tento článek obsahuje pokyny pro formátování kódu tak, aby kód jazyka F# byl:
- Čitelnější
- V souladu s konvencemi použitými pomocí nástrojů formátování v editoru Visual Studio Code a dalších editorů
- Podobně jako u jiného kódu online
Viz také konvence kódování a pokyny pro návrh komponent, které se týkají také zásad vytváření názvů.
Automatické formátování kódu
Formátovací modul kódu Fantomas je standardní nástroj komunity jazyka F# pro automatické formátování kódu. Výchozí nastavení odpovídá tomuto průvodci stylem.
Důrazně doporučujeme použít tento formátovací modul kódu. V rámci týmů F# by měly být specifikace formátování kódu dohodnuty a kodifikovány z hlediska souboru dohodnutých nastavení pro formátovací modul kódu, který je vrácen do úložiště týmu.
Obecná pravidla pro formátování
Jazyk F# ve výchozím nastavení používá významné prázdné znaky a je citlivý na prázdné znaky. Následující pokyny jsou určeny k tomu, aby poskytovaly pokyny týkající se toho, jak si poradit s některými výzvami, které to může představovat.
Použití mezer, nikoli tabulátorů
V případě potřeby odsazení je nutné použít mezery, nikoli tabulátory. Kód F# nepoužívá tabulátory a kompilátor zobrazí chybu, pokud je znak tabulátoru zjištěn mimo řetězcový literál nebo komentář.
Použití konzistentního odsazení
Při odsazení je vyžadováno aspoň jedno místo. Vaše organizace může vytvořit standardy kódování, které určují počet mezer, které se mají použít pro odsazení; dvě, tři nebo čtyři mezery odsazení na každé úrovni, kde dochází k odsazení, je typické.
Pro odsazení doporučujeme čtyři mezery.
To znamená, že odsazení programů je subjektivní záležitost. Varianty jsou v pořádku, ale první pravidlo, které byste měli dodržovat, je konzistence odsazení. Zvolte obecně uznávaný styl odsazení a systematicky ho používejte v celém základu kódu.
Vyhněte se formátování, které je citlivé na délku názvu.
Snažte se vyhnout odsazení a zarovnání, které je citlivé na pojmenování:
// ✔️ 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 _ -> ()
| ...
Hlavními důvody, proč se tomu vyhnout, jsou:
- Důležitý kód se přesune úplně doprava.
- Pro skutečný kód zbývá menší šířka.
- Přejmenování může přerušit zarovnání.
Vyhněte se nadbytečným prázdným znakům
Vyhněte se nadbytečným prázdným znakům v kódu jazyka F#, s výjimkou místa popsaného v tomto průvodci stylem.
// ✔️ OK
spam (ham 1)
// ❌ Not OK
spam ( ham 1 )
Formátování komentářů
Upřednostňujte více komentářů s dvojitým lomítkem před blokovým komentářem.
// 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.
*)
Komentáře by měly obsahovat velká písmena prvního písmena a měly by být frází nebo věty ve správném formátu.
// ✔️ A good comment.
let f x = x + 1 // Increment by one.
// ❌ two poor comments
let f x = x + 1 // plus one
Formátování komentářů dokumentu XML najdete v části "Deklarace formátování" níže.
Formátování výrazů
Tato část popisuje formátování výrazů různých druhů.
Formátování řetězcových výrazů
Řetězcové literály a interpolované řetězce lze nechat na jednom řádku bez ohledu na délku řádku.
let serviceStorageConnection =
$"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"
Interpolované výrazy s více řádky se nedoporučuje. Místo toho vytvořte vazbu výsledku výrazu na hodnotu a použijte ji v interpolovaném řetězci.
Formátování výrazů řazené kolekce členů
Vytvoření instance řazené kolekce členů by mělo být závorky a za oddělovači v ní by měla následovat jedna mezera, například: (1, 2)
, (x, y, z)
.
// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]
Běžně se akceptuje vynechání závorek ve vzorových shodách řazených kolekcí členů:
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
Pokud je řazená kolekce členů návratovou hodnotou funkce, běžně se také přijímá jako vynechání závorek:
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
V souhrnu preferujte vytváření instancí řazené kolekce členů závorek, ale při použití řazených kolekcí členů pro porovnávání vzorů nebo návratové hodnoty se považuje za v pořádku, abyste se vyhnuli závorkách.
Formátování výrazů aplikace
Při formátování funkce nebo aplikace metody jsou argumenty k dispozici na stejném řádku, pokud šířka řádku umožňuje:
// ✔️ OK
someFunction1 x.IngredientName x.Quantity
Vynechání závorek, pokud argumenty nevyžadují:
// ✔️ OK
someFunction1 x.IngredientName
// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)
// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)
Při vyvolání s více složenými argumenty nepoužívejte mezery:
// ✔️ 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)
Ve výchozích konvencích formátování se při použití funkcí malých písmen u řazených nebo závorek přidá mezera (i když se použije jeden argument):
// ✔️ 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)
Ve výchozích konvencích formátování se při použití velkých písmen u argumentů řazených na řazené argumenty nepřidá mezera. Důvodem je to, že se často používají s fluent programováním:
// ✔️ 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)
Možná budete muset funkci na novém řádku předat jako čitelnost nebo protože seznam argumentů nebo názvy argumentů jsou příliš dlouhé. V takovém případě odsadíte jednu úroveň:
// ✔️ 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)
Když funkce vezme jeden víceřádkový řazený argument, umístěte každý argument na nový řádek:
// ✔️ 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)
Pokud jsou výrazy argumentů krátké, oddělte argumenty mezerami a ponechejte je na jednom řádku.
// ✔️ OK
let person = new Person(a1, a2)
// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)
// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)
Pokud jsou výrazy argumentu dlouhé, použijte nové čáry a odsazení o jednu úroveň místo odsazení do levé závorky.
// ✔️ 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)
Stejná pravidla platí i v případě, že existuje pouze jeden víceřádkový argument, včetně víceřádkových řetězců:
// ✔️ 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" ]
)
Formátování výrazů kanálu
Při použití více řádků by operátory kanálu |>
měly jít pod výrazy, na kterých pracují.
// ✔️ 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
Formátování výrazů lambda
Pokud se výraz lambda používá jako argument ve výrazu s více řádky a následuje další argumenty, umístěte text výrazu lambda na nový řádek odsazený o jednu úroveň:
// ✔️ OK
let printListWithOffset a list1 =
List.iter
(fun elem ->
printfn $"A very long line to format the value: %d{a + elem}")
list1
Pokud je argument lambda posledním argumentem v aplikaci funkcí, umístěte všechny argumenty do šipek na stejný řádek.
// ✔️ 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}")
Zacházejte s lambda podobným způsobem.
// ✔️ OK
functionName arg1 arg2 arg3 (function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Pokud existuje mnoho počátečních nebo víceřádkových argumentů před odsazením všechargumentch
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Pokud je text výrazu lambda dlouhých více řádků, měli byste zvážit refaktoring na místně vymezenou funkci.
Pokud kanály obsahují výrazy lambda, každý výraz lambda je obvykle posledním argumentem v každé fázi kanálu:
// ✔️ 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}")
Pokud se argumenty lambda nevejdou na jeden řádek nebo jsou víceřádkové, umístěte je na další řádek odsazený o jednu úroveň.
// ✔️ 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 ()
Formátování aritmetických a binárních výrazů
Vždy používejte prázdné znaky kolem binárních aritmetických výrazů:
// ✔️ OK
let subtractThenAdd x = x - 1 + 3
Při kombinaci s určitými možnostmi formátování by se nepodařilo obklopit binární -
operátor, což by mohlo vést k jeho interpretaci jako unárního -
.
Unární -
operátory by měly vždy následovat za hodnotou, kterou negují:
// ✔️ OK
let negate x = -x
// ❌ Not OK
let negateBad x = - x
Přidání prázdného znaku -
za operátorem může vést k nejasnostem pro ostatní.
Oddělte binární operátory mezerami. Výrazy infixu jsou v pořádku pro řádkování ve stejném sloupci:
// ✔️ OK
let function1 () =
acc +
(someFunction
x.IngredientName x.Quantity)
// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
arg1 + arg2 +
arg3 + arg4
Toto pravidlo platí také pro jednotky měr v typech a konstantních poznámkách:
// ✔️ 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)> }
Následující operátory jsou definovány ve standardní knihovně jazyka F# a měly by se používat místo definování ekvivalentů. Použití těchto operátorů se doporučuje, protože má tendenci vytvářet kód čitelnější a idiomatice. Následující seznam shrnuje doporučené operátory jazyka F#.
// ✔️ 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
Výrazy operátoru formátování rozsahu
Mezery kolem ..
všech výrazů přidáte jenom v případech, kdy nejsou atomické.
Celočíselné a jednoslovné identifikátory jsou považovány za atomické.
// ✔️ 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 ]
Tato pravidla platí také pro řezy:
// ✔️ OK
arr[0..10]
list[..^1]
Formátování výrazů if
Odsazení podmíněných výrazů závisí na velikosti a složitosti výrazů, které je tvoří. Napište je na jeden řádek, když:
cond
e2
ae1
jsou krátké.e1
ae2
nejsouif/then/else
samy o sobě výrazy.
// ✔️ OK
if cond then e1 else e2
Pokud chybí výraz else, doporučuje se nikdy napsat celý výraz na jeden řádek. Jedná se o rozlišení imperativního kódu od funkce.
// ✔️ OK
if a then
()
// ❌ Not OK, code formatters will reformat to the above by default
if a then ()
Pokud je některý z výrazů víceřádkový, každá podmíněná větev by měla být víceřádkové.
// ✔️ OK
if cond then
let e1 = something()
e1
else
e2
// ❌ Not OK
if cond then
let e1 = something()
e1
else e2
Více podmíněných výrazů se stejným oborem elif
if
a else
odsazením se odsadí podle pravidel jednořádkových if/then/else
výrazů.
// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4
Pokud je některá z podmínek nebo výrazů víceřádkový, celý if/then/else
výraz je víceřádkový:
// ✔️ 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
Pokud je podmínka víceřádkové nebo překračuje výchozí toleranci jednoho řádku, výraz podmínky by měl používat jedno odsazení a nový řádek.
Klíčové if
slovo by then
se mělo zarovnat při zapouzdření dlouhého výrazu podmínky.
// ✔️ 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
Je však lepším stylem refaktorovat dlouhé podmínky na letovou vazbu nebo samostatnou funkci:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
if performAction then
e1
else
e2
Formátování výrazů sjednocovacího případu
Použití diskriminovaných případů sjednocení se řídí stejnými pravidly jako aplikace funkcí a metod. To znamená, že název je velkými písmeny, a proto formátování kódu odebere mezeru před řazenou kolekcí členů:
// ✔️ OK
let opt = Some("A", 1)
// OK, but code formatters will remove the space
let opt = Some ("A", 1)
Podobně jako aplikace funkcí by konstrukce rozdělené na více řádků měly používat odsazení:
// ✔️ OK
let tree1 =
BinaryNode(
BinaryNode (BinaryValue 1, BinaryValue 2),
BinaryNode (BinaryValue 3, BinaryValue 4)
)
Formátování výrazů seznamu a polí
Psaní x :: l
s mezerami kolem operátoru ::
(::
je operátor infix, tedy obklopen mezerami).
Seznam a pole deklarovaná na jednom řádku by měly mít mezeru za levou závorkou a před pravou závorkou:
// ✔️ OK
let xs = [ 1; 2; 3 ]
// ✔️ OK
let ys = [| 1; 2; 3; |]
Vždy používejte alespoň jednu mezeru mezi dvěma odlišnými operátory typu složená závorka. Například ponechte mezeru mezi znakem a [
znakem {
.
// ✔️ 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 }]
Stejné pokyny platí pro seznamy nebo pole řazených kolekcí členů.
Seznamy a pole rozdělená na více řádků se řídí podobným pravidlem jako záznamy:
// ✔️ 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 |] |]
Stejně jako u záznamů bude deklarování levých a uzavíracích závorek na vlastním řádku usnadnit přesouvání kódu do funkcí a jejich propojení:
// ✔️ 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 |]
|]
Pokud je výraz seznamu nebo pole pravou stranou vazby, můžete raději použít Stroustrup
styl:
// ✔️ 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 |]
|]
Pokud však výraz seznamu nebo pole není pravou stranou vazby, například když je uvnitř jiného seznamu nebo pole, pokud tento vnitřní výraz potřebuje přesahovat více řádků, měly by hranaté závorky jít na vlastní řádky:
// ✔️ 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
] ]
Stejné pravidlo platí pro typy záznamů uvnitř polí a seznamů:
// ✔️ 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
} ]
Při programovém generování polí a seznamů upřednostňujte ->
do ... yield
před tím, kdy se hodnota vždy generuje:
// ✔️ 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 ]
Starší verze jazyka F# se vyžadují při yield
určování v situacích, kdy mohou být data vygenerována podmíněně, nebo se můžou vyhodnocovat po sobě jdoucí výrazy. Upřednostněte vynechání těchto yield
klíčových slov, pokud není nutné zkompilovat starší jazykovou verzi jazyka 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"
]
V některých případech do...yield
může pomoct čitelnost. Tyto případy, i když subjektivní, je třeba vzít v úvahu.
Formátování výrazů záznamů
Krátké záznamy lze zapsat na jeden řádek:
// ✔️ OK
let point = { X = 1.0; Y = 0.0 }
Záznamy, které jsou delší, by měly pro popisky používat nové řádky:
// ✔️ OK
let rainbow =
{ Boss = "Jeffrey"
Lackeys = ["Zippy"; "George"; "Bungle"] }
Styly formátování víceřádkových závorek
Pro záznamy, které pokrývají více řádků, existují tři běžně používané styly formátování: Cramped
, Aligned
a Stroustrup
. Styl Cramped
byl výchozím stylem kódu jazyka F#, protože má tendenci upřednostnit styly, které kompilátoru umožňují snadno analyzovat kód. Oba Aligned
styly Stroustrup
umožňují snadnější změny pořadí členů, což vede k kódu, který může být jednodušší refaktorovat, s nevýhodou, že některé situace mohou vyžadovat trochu více podrobného kódu.
Cramped
: Historický standard a výchozí formát záznamu jazyka F#. Levá závorka se jdou na stejný řádek jako první člen, pravá hranatá závorka na stejném řádku jako poslední člen.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Aligned
: Závorky závorky získají vlastní čáru, která je zarovnaná na stejném sloupci.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }
Stroustrup
: Levá hranatá závorka jde na stejnou čáru jako vazba, pravá hranatá závorka získá vlastní čáru.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }
Stejná pravidla stylu formátování platí pro prvky seznamu a pole.
Formátování výrazů záznamů kopírování a aktualizace
Výraz záznamu pro kopírování a aktualizaci je stále záznamem, takže platí podobné pokyny.
Krátké výrazy se dají přizpůsobit na jeden řádek:
// ✔️ OK
let point2 = { point with X = 1; Y = 2 }
Delší výrazy by měly používat nové řádky a formátovat na základě jedné z výše uvedených konvencí:
// ✔️ 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 = ""
}
}
Poznámka: Pokud používáte Stroustrup
styl pro výrazy kopírování a aktualizace, je nutné odsadit členy dál než název zkopírovaného záznamu:
// ✔️ 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"
}
Porovnávání vzorů formátování
|
Použijte pro každou klauzuli shody bez odsazení. Pokud je výraz krátký, můžete zvážit použití jednoho řádku, pokud je každý dílčí výraz také jednoduchý.
// ✔️ 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"
Pokud je výraz na pravé straně šipky porovnávání vzorů příliš velký, přesuňte ho na následující řádek odsazením jednoho kroku odsazením match
/|
.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Podobně jako u velkých podmínek, pokud je výraz shody víceřádkový nebo překračuje výchozí toleranci jednořádkového výrazu, výraz shody by měl použít jedno odsazení a nový řádek.
Klíčové match
slovo by with
se mělo zarovnat při zapouzdření dlouhého výrazu shody.
// ✔️ OK, but better to refactor, see below
match
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
with
| X y -> y
| _ -> 0
Je to však lepší styl refaktorování dlouhých shod výrazů na vazbu let nebo samostatnou funkci:
// ✔️ OK
let performAction =
complexExpression a b && env.IsDevelopment()
|| someFunctionToCall
aVeryLongParameterNameOne
aVeryLongParameterNameTwo
aVeryLongParameterNameThree
match performAction with
| X y -> y
| _ -> 0
Zarovnávání šipek shody vzorku by se mělo vyhnout.
// ✔️ 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
Porovnávání vzorů zavedených pomocí klíčového slova function
by mělo odsadit jednu úroveň od začátku předchozího řádku:
// ✔️ OK
lambdaList
|> List.map (function
| Abs(x, body) -> 1 + sizeLambda 0 body
| App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
| Var v -> 1)
Použití ve funkcích definovaných function
let
nebo let rec
obecně by se mělo vyhnout ve prospěch match
. Při použití by pravidla vzoru měla odpovídat klíčovému slovu 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
Formátování try/with expressions
Vzor odpovídající typu výjimky by měl být odsazený na stejné úrovni jako 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"
|
Přidejte pro každou klauzuli s výjimkou jediné klauzule:
// ✔️ 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
Formátování pojmenovaných argumentů
Pojmenované argumenty by měly obsahovat mezery kolem =
:
// ✔️ 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)
Při porovnávání vzorů pomocí diskriminovaných sjednocení se pojmenované vzory formátují podobně, například.
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
Formátovánívýrazch
Výrazy location <- expr
s mutací jsou obvykle formátovány na jednom řádku.
Pokud je požadováno víceřádkové formátování, umístěte pravý výraz na nový řádek.
// ✔️ 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
Formátování výrazů objektů
Členy výrazu objektu by měly být zarovnány s member
odsazením o jednu úroveň.
// ✔️ 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) }
Můžete také raději použít Stroustrup
styl:
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)
}
Definice prázdného typu můžou být formátovány na jednom řádku:
type AnEmptyType = class end
Bez ohledu na zvolenou šířku = class end
stránky by měl být vždy na stejném řádku.
Formátování výrazů indexu nebo řezu
Výrazy indexu by neměly obsahovat žádné mezery kolem levých a pravých závorek.
// ✔️ OK
let v = expr[idx]
let y = myList[0..1]
// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]
To platí i pro starší expr.[idx]
syntaxi.
// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]
// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]
Formátování uvozových výrazů
Symboly oddělovače (<@
, @>
, <@@
, @@>
) by měly být umístěny na samostatných řádcích, pokud je uvozovací výraz víceřádkovým výrazem.
// ✔️ OK
<@
let f x = x + 10
f 20
@>
// ❌ Not OK
<@ let f x = x + 10
f 20
@>
V jednořádkových výrazech by se symboly oddělovače měly umístit na stejný řádek jako samotný výraz.
// ✔️ OK
<@ 1 + 1 @>
// ❌ Not OK
<@
1 + 1
@>
Formátování zřetězených výrazů
Pokud jsou zřetězený výrazy (aplikace funkcí propojené s .
) dlouhé, vložte každou vyvolání aplikace na další řádek.
Odsazení následných propojení v řetězci o jednu úroveň za úvodním propojením
// ✔️ OK
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
// ✔️ OK
Cli
.Wrap("git")
.WithArguments(arguments)
.WithWorkingDirectory(__SOURCE_DIRECTORY__)
.ExecuteBufferedAsync()
.Task
Úvodní odkaz se může skládat z více odkazů, pokud se jedná o jednoduché identifikátory. Například přidání plně kvalifikovaného oboru názvů.
// ✔️ OK
Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())
Další odkazy by také měly obsahovat jednoduché identifikátory.
// ✔️ 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))
Pokud se argumenty uvnitř aplikace funkcí nevejdou na zbytek řádku, vložte každý argument na další řádek.
// ✔️ 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
Argumenty lambda uvnitř aplikace funkcí by měly začínat na stejném řádku jako levá (
.
// ✔️ OK
builder
.WithEnvironment()
.WithLogger(fun loggerConfiguration ->
// ...
())
// ❌ Not OK, formatting tools will reformat to the above
builder
.WithEnvironment()
.WithLogger(
fun loggerConfiguration ->
// ...
())
Deklarace formátování
Tato část popisuje deklarace formátování různých druhů.
Přidání prázdných řádků mezi deklaracemi
Oddělte definice funkcí nejvyšší úrovně a tříd jedním prázdným řádkem. Příklad:
// ✔️ 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
Pokud má konstruktor komentáře dokumentu XML, přidejte před komentář prázdný řádek.
// ✔️ OK
/// This is a function
let thisFunction() =
1 + 1
/// This is another function, note the blank line before this line
let thisFunction() =
1 + 1
Formátování deklarací let a členů
Při formátování let
a member
deklarací obvykle pravá strana vazby přejde na jeden řádek nebo (pokud je příliš dlouhá), přejde na nový řádek odsazený o jednu úroveň.
Například následující příklady jsou kompatibilní:
// ✔️ 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
Nedodržují předpisy:
// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""
let d = while f do
printfn "%A" x
Vytváření instancí typu záznamu může také umístit závorky na vlastní řádky:
// ✔️ OK
let bilbo =
{
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Můžete také raději použít Stroustrup
styl s otevřením {
na stejném řádku jako název vazby:
// ✔️ OK
let bilbo = {
Name = "Bilbo"
Age = 111
Region = "The Shire"
}
Členy oddělte jedním prázdným řádkem a dokumentem a přidejte komentář k dokumentaci:
// ✔️ OK
/// This is a thing
type ThisThing(value: int) =
/// Gets the value
member _.Value = value
/// Returns twice the value
member _.TwiceValue() = value*2
Nadbytečné prázdné řádky je možné použít (střídmě) k oddělení skupin souvisejících funkcí. Prázdné řádky mohou být vynechány mezi řadu souvisejících jednořádkových řádků (například sada fiktivních implementací). K označení logických oddílů používejte prázdné řádky ve funkcích, střídmě.
Formátování argumentů funkce a členů
Při definování funkce používejte kolem každého argumentu prázdné znaky.
// ✔️ 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
Pokud máte dlouhou definici funkce, umístěte parametry na nové řádky a odsaďte je tak, aby odpovídaly úrovni odsazení následného parametru.
// ✔️ 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
To platí také pro členy, konstruktory a parametry pomocí řazených kolekcí členů:
// ✔️ 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
Pokud jsou parametry curried, umístěte =
znak spolu s libovolným návratovým typem na nový řádek:
// ✔️ 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
Toto je způsob, jak se vyhnout příliš dlouhým řádkům (v případě, že návratový typ může mít dlouhý název) a při přidávání parametrů má menší poškození řádku.
Deklarace operátoru formátování
Volitelně můžete k ohraničení definice operátoru použít prázdné znaky:
// ✔️ OK
let ( !> ) x f = f x
// ✔️ OK
let (!>) x f = f x
Pro jakýkoli vlastní operátor, který začíná *
a který má více než jeden znak, musíte na začátek definice přidat prázdné znaky, aby se zabránilo nejednoznačnosti kompilátoru. Z tohoto důvodu doporučujeme jednoduše obklopit definice všech operátorů jediným prázdným znakem.
Formátování deklarací záznamů
U deklarací záznamů byste ve výchozím nastavení měli odsadit {
definici typu o čtyři mezery, spustit seznam popisků na stejném řádku a zarovnat členy, pokud existuje, s tokenem {
:
// ✔️ OK
type PostalAddress =
{ Address: string
City: string
Zip: string }
Je také běžné dát přednost umístění závorek na vlastní řádek s popisky odsazenými dalšími čtyřmi mezerami:
// ✔️ OK
type PostalAddress =
{
Address: string
City: string
Zip: string
}
Můžete také umístit {
na konec prvního řádku definice typu (Stroustrup
styl):
// ✔️ OK
type PostalAddress = {
Address: string
City: string
Zip: string
}
Pokud jsou potřeba další členové, nepoužívejte with
/end
je, kdykoli je to možné:
// ✔️ 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
Výjimkou tohoto pravidla stylu je, pokud formátujete záznamy podle Stroustrup
stylu. V takovém případě se kvůli pravidlům kompilátoru with
vyžaduje klíčové slovo, pokud chcete implementovat rozhraní nebo přidat další členy:
// ✔️ 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}"
Při přidání dokumentace XML pro pole Aligned
záznamů nebo Stroustrup
styl je upřednostňovaný a mezi členy by se měly přidat další prázdné znaky:
// ❌ 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}"
Umístění počátečního tokenu na nový řádek a koncového tokenu na nový řádek je vhodnější, pokud deklarujete implementace rozhraní nebo členy záznamu:
// ✔️ 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
Stejná pravidla platí pro aliasy anonymního typu záznamu.
Formátování diskriminovaných deklarací sjednocení
U diskriminovaných deklarací sjednocení odsazení |
definice typu o čtyři mezery:
// ✔️ 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
Pokud existuje jedna krátká sjednocení, můžete vynechat přední |
.
// ✔️ OK
type Address = Address of string
Pro delší nebo víceřádkové sjednocení ponechte |
a umístěte každé sjednocující pole na nový řádek s oddělením *
na konci každého řádku.
// ✔️ 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
Po přidání komentářů k dokumentaci použijte před každý ///
komentář prázdný řádek.
// ✔️ 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
Formátování deklarací literálů
Literály jazyka Literal
F# používající atribut by měly umístit atribut na vlastní řádek a používat pojmenování PascalCase:
// ✔️ OK
[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__
[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"
Vyhněte se umístění atributu na stejný řádek jako hodnota.
Deklarace modulu formátování
Kód v místním modulu musí být odsazený vzhledem k modulu, ale kód v modulu nejvyšší úrovně by neměl být odsazený. Prvky oboru názvů nemusí být odsazené.
// ✔️ 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
Deklarace formátování úkolů
V deklaracích typů, deklarací modulů a výpočetních výrazech je použití do
nebo do!
někdy vyžadováno pro vedlejší operace.
Pokud se jedná o více řádků, použijte odsazení a nový řádek, aby bylo odsazení konzistentní s let
/let!
. Tady je příklad použití do
ve třídě:
// ✔️ 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
Tady je příklad použití do!
dvou mezer odsazení (protože shodou do!
okolností není rozdíl mezi přístupy při použití čtyř mezer odsazení):
// ✔️ 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
}
Formátování operací s výpočetními výrazy
Při vytváření vlastních operací pro výpočetní výrazy se doporučuje použít pojmenování 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
}
Doména, která se modeluje, by měla nakonec řídit konvenci pojmenování. Pokud se jedná o idiomatickou použití jiné konvence, měla by se místo ní použít tato konvence.
Pokud je návratovou hodnotou výrazu výpočetní výraz, raději zadejte název klíčového slova výpočetního výrazu na vlastní řádek:
// ✔️ OK
let foo () =
async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Můžete také dát výraz výpočtů na stejný řádek jako název vazby:
// ✔️ OK
let foo () = async {
let! value = getValue()
do! somethingElse()
return! anotherOperation value
}
Podle toho, co dáváte přednost, byste měli mít za cíl zůstat konzistentní v celém základu kódu. Formátovací moduly vám můžou umožnit určit tuto předvolbu, aby zůstala konzistentní.
Typy formátování a poznámky k typům
Tato část popisuje typy formátování a poznámky k typům. To zahrnuje formátování souborů podpisů s příponou .fsi
.
U typů upřednostňujte syntaxi předpony pro obecné typy (Foo<T>
), s některými konkrétními výjimkami.
F# umožňuje jak styl přípony psaní obecných typů (například int list
), tak styl předpony (například list<int>
).
Styl přípony lze použít pouze s jedním argumentem typu.
Vždy preferujte styl .NET s výjimkou pěti konkrétních typů:
- Pro seznamy jazyka F# použijte formát přípony,
int list
nikolilist<int>
. - Pro možnosti jazyka F# použijte formát přípony,
int option
nikolioption<int>
. - Pro možnosti hodnot jazyka F# použijte formát přípony,
int voption
nikolivoption<int>
. - Pro pole jazyka F# použijte formát přípony,
int array
nikoliarray<int>
neboint[]
. - Pro odkazové buňky použijte
int ref
místoref<int>
neboRef<int>
.
Pro všechny ostatní typy použijte formulář předpony.
Typy funkcí formátování
Při definování podpisu funkce použijte kolem symbolu ->
prázdné znaky:
// ✔️ OK
type MyFun = int -> int -> string
// ❌ Not OK
type MyFunBad = int->int->string
Formátování hodnot a poznámek typu argumentu
Při definování hodnot nebo argumentů pomocí poznámek typu použijte prázdné znaky za :
symbolem, ale ne před:
// ✔️ 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
Formátování poznámek víceřádkového typu
Pokud je poznámka typu dlouhá nebo víceřádkové, umístěte je na další řádek odsazený o jednu úroveň.
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
U vložených anonymních typů záznamů můžete také použít Stroustrup
styl:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Formátování návratových poznámek k typu
Ve funkci nebo návratových poznámkách typu člen použijte prázdné znaky před a za :
symbolem:
// ✔️ 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
Typy formátování v podpisech
Při psaní úplných typů funkcí v podpisech je někdy nutné rozdělit argumenty na více řádků. Návratový typ je vždy odsazený.
U řazené funkce jsou argumenty odděleny , *
umístěné na konci každého řádku.
Představte si například funkci s následující implementací:
let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...
V odpovídajícím souboru podpisu (.fsi
přípony) lze funkci naformátovat následujícím způsobem, pokud je vyžadováno víceřádkové formátování:
// ✔️ OK
val SampleTupledFunction:
arg1: string *
arg2: string *
arg3: int *
arg4: int ->
int list
Podobně zvažte složenou funkci:
let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...
V odpovídajícím souboru podpisu se umístí ->
na konec každého řádku:
// ✔️ OK
val SampleCurriedFunction:
arg1: string ->
arg2: string ->
arg3: int ->
arg4: int ->
int list
Podobně zvažte funkci, která přebírá kombinaci složených a řazených argumentů:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
V odpovídajícím souboru podpisu se odsadí typy předcházející řazené kolekci členů.
// ✔️ 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
Stejná pravidla platí pro členy v podpisech typů:
type SampleTypeName =
member ResolveDependencies:
arg1: string *
arg2: string ->
string
Formátování explicitních argumentů a omezení obecného typu
Níže uvedené pokyny platí pro definice funkcí, definice členů, definice typů a aplikace funkcí.
Pokud nejsou příliš dlouhé, ponechte argumenty a omezení obecného typu na jednom řádku:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
// function body
Pokud se oba argumenty/omezení obecného typu a parametry funkce nevejdou, ale parametry typu nebo omezení pouze dělají, umístěte parametry na nové řádky:
// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
param
=
// function body
Pokud jsou parametry typu nebo omezení příliš dlouhé, zalomte je a zarovnejte je, jak je znázorněno níže. Seznam parametrů typu ponechte na stejném řádku jako funkce bez ohledu na jeho délku. U omezení umístěte when
na první řádek každé omezení a ponechte každé omezení na jednom řádku bez ohledu na jeho délku. Umístěte >
na konec posledního řádku. Odsazení omezení o jednu úroveň
// ✔️ 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
Pokud jsou parametry nebo omezení typu rozdělené, ale neexistují žádné normální parametry funkce, umístěte ho =
na nový řádek bez ohledu na to:
// ✔️ 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
Stejná pravidla platí pro aplikace funkcí:
// ✔️ 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
Dědičnost formátování
Argumenty konstruktoru základní třídy se zobrazí v seznamu argumentů v klauzuli inherit
.
Vložte klauzuli inherit
na nový řádek odsazený o jednu úroveň.
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)
Pokud je konstruktor dlouhý nebo víceřádkový, umístěte je na další řádek odsazený o jednu úroveň.
Naformátujte tento víceřádkový konstruktor podle pravidel víceřádkových aplikací funkcí.
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
""")
Formátování primárního konstruktoru
Ve výchozích konvencích formátování se mezi název typu a závorky primárního konstruktoru nepřidá mezera.
// ✔️ OK
type MyClass() =
class
end
type MyClassWithParams(x: int, y: int) =
class
end
// ❌ Not OK
type MyClass () =
class
end
type MyClassWithParams (x: int, y: int) =
class
end
Více konstruktorů
inherit
Pokud je klauzule součástí záznamu, umístěte ji na stejný řádek, pokud je krátká.
A umístěte ho na další řádek, odsazený o jednu úroveň, pokud je dlouhý nebo víceřádkový.
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 }
Atributy formátování
Atributy jsou umístěny nad konstruktorem:
// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...
// ✔️ OK
[<RequireQualifiedAccess>]
module M =
let f x = x
// ✔️ OK
[<Struct>]
type MyRecord =
{ Label1: int
Label2: string }
Měly by jít po jakékoli dokumentaci XML:
// ✔️ OK
/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
let f x = x
Formátování atributů u parametrů
Atributy lze také umístit na parametry. V tomto případě umístěte na stejný řádek jako parametr a před název:
// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)
Formátování více atributů
Pokud se u konstruktoru, který není parametrem, použije více atributů, umístěte každý atribut na samostatný řádek:
// ✔️ OK
[<Struct>]
[<IsByRefLike>]
type MyRecord =
{ Label1: int
Label2: string }
Při použití u parametru umístěte atributy na stejný řádek a oddělte je oddělovačem ;
.
Poděkování
Tyto pokyny vycházejí z komplexního průvodce konvencemi formátování jazyka F# od Anh-Dung Phan.