Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek obsahuje pokyny pro formátování kódu tak, aby kód jazyka F# byl:
- Čitelnější
- V souladu s konvencemi, které uplatňují nástroje pro formátování ve Visual Studio Code a dalších editorech
- Podobně jako u jiného online kódu
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 jako dohodnutý soubor nastavení pro kódový formátovač, který je uložen do týmového úložiště.
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žívejte mezery, ne tabulátory
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žívejte konzistentní odsazení
Při odsazení je vyžadována alespoň jedna mezera. 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é je třeba 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.
Vyhněte se odsazení a zarovnání, které je citlivé na názvosloví.
// ✔️ 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á méně místa.
- 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 začínat velkým písmenem a být dobře formulovanými frázemi nebo větami.
// ✔️ 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}"
Nedoporučuje se používat interpolované výrazy s více řádky. 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ů n-tice
Vytvoření instance n-tice by mělo být v závorkách a za oddělovacími čárkami 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é shodě tuply.
// ✔️ OK
let (x, y) = z
let x, y = z
// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1
Je také běžně přijímáno vynechat závorky, pokud je n-tice návratovou hodnotou funkce.
// ✔️ OK
let update model msg =
match msg with
| 1 -> model + 1, []
| _ -> model, [ msg ]
V souhrnu preferujte vytváření instancí n-tic se závorkami, ale při použití n-tic pro porovnávání vzorů nebo jako návratové hodnoty je považováno za v pořádku nepoužití závorek.
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í pro převod na malá písmena u tuplů nebo uzavřených v závorkách 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í metod s velkými písmeny na n-tice argumentů nepřidává mezera. Důvodem je to, že se často používají s plynulým 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)
Stejné konvence formátování platí pro porovnávání vzorů. Formátování hodnot F# stylu konzistentně:
// ✔️ OK - Consistent formatting for expressions and patterns
let result = Some(value)
match result with
| Some(x) -> x
| None -> 0
Možná budete muset předat argumenty funkci na novém řádku kvůli čitelnosti nebo protože seznam argumentů či názvy argumentů jsou příliš dlouhé. V takovém případě odsaď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é řádky a odsaďte o jednu úroveň místo odsazení k levé závorkě (.
// ✔️ 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 být umístěny 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
U operátorů reverzního kanálu <| ponechte krátké výrazy na jednom řádku. Pokud délka řádku vyžaduje zalamování, umístěte argumenty na nové řádky a zarovnejte je konzistentně:
// ✔️ OK - short expressions stay on one line
let result = someFunction <| arg1 <| arg2 <| arg3
// ✔️ OK - longer expressions can wrap when necessary
failwith
<| sprintf "A very long error message that exceeds reasonable line length: %s - additional details: %s"
longVariableName
anotherLongVariableName
// ✔️ OK - align continuation lines with the operator
let longResult =
someVeryLongFunctionName
<| firstVeryLongArgumentName
<| secondVeryLongArgumentName
<| thirdVeryLongArgumentName
// ❌ Not OK - unnecessary wrapping of short expressions
failwith <| sprintf "short: %s"
value
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 na stejný řádek až po šipku.
// ✔️ 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 lambda, odsazte všechny argumenty o jednu úroveň.
// ✔️ OK
functionName
arg1
arg2
arg3
(fun arg4 ->
bodyExpr)
// ✔️ OK
functionName
arg1
arg2
arg3
(function
| Choice1of2 x -> 1
| Choice2of2 y -> 2)
Pokud má tělo lambda výrazu více řádků, měli byste zvážit přeměnu na lokálně definovanou 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í líných výrazů
Při psaní jednořádkových opožděných výrazů mějte všechno na jednom řádku:
// ✔️ OK
let x = lazy (computeValue())
// ✔️ OK
let y = lazy (a + b)
U víceřádkových opožděných výrazů umístěte otevřenou závorku na stejný řádek jako klíčové slovo lazy, tělo výrazu odsazujte o jednu úroveň a uzavřenou závorku zarovnejte s otevřenou závorkou:
// ✔️ OK
let v =
lazy (
// some code
let x = computeExpensiveValue()
let y = computeAnotherValue()
x + y
)
// ✔️ OK
let handler =
lazy (
let connection = openConnection()
let data = fetchData connection
processData data
)
To se řídí stejným vzorem jako jiné aplikace funkcí s víceřádkovými argumenty. Levá závorka zůstane s lazya výraz se odsadí o jednu úroveň.
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. Infixové výrazy je v pořádku zarovnat do stejného sloupce:
// ✔️ 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 idiomatičtější. Následující seznam shrnuje doporučené operátory jazyka F#.
// ✔️ OK
x |> f // Forward pipeline
f <| x // Reverse pipeline
f >> g // Forward composition
x |> ignore // Discard away a value
x + y // Overloaded addition (including string concatenation)
x - y // Overloaded subtraction
x * y // Overloaded multiplication
x / y // Overloaded division
x % y // Overloaded modulus
x && y // Lazy/short-cut "and"
x || y // Lazy/short-cut "or"
x <<< y // Bitwise left shift
x >>> y // Bitwise right shift
x ||| y // Bitwise or, also for working with “flags” enumeration
x &&& y // Bitwise and, also for working with “flags” enumeration
x ^^^ y // Bitwise xor, also for working with “flags” enumeration
Výrazy operátoru formátování rozsahu
Mezery kolem výrazu .. přidejte pouze v případě, že některý výraz není 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ž:
-
conde1ae2jsou krátké. -
e1ae2nejsouif/then/elsesamy o sobě výrazy.
// ✔️ OK
if cond then e1 else e2
Pokud je výraz else vynechán, doporučuje se nikdy nepsat 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 jsou některé z výrazů víceřádkové, každá podmínková 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
Podmíněné výrazy s elif a else jsou odsazeny ve stejném rozsahu jako if, pokud splňují pravidla jednořádkových výrazů if/then/else.
// ✔️ 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é slovo if a then by mělo být zarovnáno 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 s velkým písmenem, a proto nástroje pro formátování kódu odstraní mezeru před n-ticí.
// ✔️ 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 [ a {.
// ✔️ 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 n-tic.
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 rekordů, deklarování otevíracích a uzavíracích závorek na samostatném řádku usnadní přesouvání kódu a jeho propojení s funkcemi.
// ✔️ 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 pokud se nachází uvnitř jiného seznamu nebo pole, a pokud musí tento vnitřní výraz zasahovat do více řádků, měly by být závorky umístěny 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 -> před do ... yield, pokud 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# vyžadovaly specifikaci yield v situacích, kdy data mohou být generována podmíněně, nebo kdy mohou být vyhodnocovány 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 může do...yield pomoct s č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, Aligneda 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#. Kulatá závorka jde na stejný řádek jako první prvek, pravá kulatá závorka na stejném řádku jako poslední prvek.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = [ "Zippy"; "George"; "Bungle" ] }Aligned: Závorky jsou každá na samostatné řádce, zarovnané ve stejném sloupci.let rainbow = { Boss1 = "Jeffrey" Boss2 = "Jeffrey" Boss3 = "Jeffrey" Lackeys = ["Zippy"; "George"; "Bungle"] }Stroustrup: Otevírací závorka je na stejném řádku jako vazba, uzavírací závorka bude na samostatném řádku.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ů typu kopírování a aktualizace
Výraz záznamu pro kopírování a aktualizaci je stále záznamem, takže platí podobná pravidla.
Krátké výrazy se vejdou 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 pro kopírování a aktualizaci, 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"
Formátování při porovnávání vzorů by mělo být konzistentní s formátováním výrazů. Nepřidávejte mezeru před levou závorku parametrů vzoru.
// ✔️ OK
match x with
| Some(y) -> y
| None -> 0
// ✔️ OK
match data with
| Success(value) -> value
| Error(msg) -> failwith msg
// ❌ Not OK, pattern formatting should match expression formatting
match x with
| Some (y) -> y
| None -> 0
V vzorech však používejte mezery mezi samostatnými složenými argumenty, stejně jako ve výrazech:
// ✔️ OK - space between curried arguments
match x with
| Pattern arg (a, b) -> processValues arg a b
// ❌ Not OK - missing space between curried arguments
match x with
| Pattern arg(a, b) -> processValues arg a b
Pokud je výraz na pravé straně šipky porovnávání vzorů příliš velký, přesuňte ho na následující řádek a odsaďte o jednu úroveň od match/|.
// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
1 + sizeLambda body
| App(lam1, lam2) ->
sizeLambda lam1 + sizeLambda lam2
Podobně jako u rozsáhlých podmínek if, pokud je výraz match víceřádkový nebo překračuje výchozí toleranci jednořádkového výrazu, měl by výraz match použít jedno odsazení a nový řádek.
Klíčové slovo match a with by mělo být zarovnáno při uzavř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é pomocí klíčového slova function by mělo být o jednu úroveň odsazené oproti 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í function ve funkcích definovaných pomocí let nebo let rec by se mělo obecně vyhnout a preferovat 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ýrazů mutace
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 výraz na pravé straně 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ů
Členové výrazu objektu by měli být zarovnáni tak, aby member bylo odsazeno 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í citovaný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ždé 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 jmenného prostoru.
// ✔️ 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ř volání funkce by měly začínat na stejném řádku jako otevírací (.
// ✔️ 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
Tyto nejsou v souladu s 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í). Ve funkcích používejte prázdné řádky střídmě k označení logických oddílů.
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 používající třídy:
// ✔️ 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, začít seznam popisků na stejném řádku a zarovnat členy, pokud existují, 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, 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řidáním XML dokumentace ke záznamovým polím je preferován styl Aligned nebo Stroustrup a mezi členy by mělo být přidáno více mezer.
// ❌ 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í deklarací diskriminované unie
U rozlišených deklarací unie odsazujte | v definici 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 jedno krátké unium, můžete vynechat počáteční |.
// ✔️ 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 F# používající atribut Literal by měly umístit atribut na samostatný řá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
Formátování deklarací
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 text přesahuje více řádků, použijte odsazení a nový řádek, aby odsazení zůstalo 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 je idiomatické použít jinou konvenci, měla by se místo toho použít.
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
}
Také můžete upřednostnit umístění výpočetního výrazu na stejný řádek jako název proměnné 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 postfixový styl zápisu obecných typů (například int list), tak prefixový styl (například list<int>).
Styl přípony lze použít pouze s jedním argumentem typu.
Vždy preferujte styl .NET s výjimkou šesti konkrétních typů:
- Pro seznamy jazyka F# použijte formát přípony,
int listnikolilist<int>. - Pro možnosti jazyka F# použijte formát přípony,
int optionnikolioption<int>. - Pro možnosti hodnot jazyka F# použijte formát přípony,
int voptionnikolivoption<int>. - Pro pole jazyka F# použijte formát přípony,
int arraynikoliarray<int>neboint[]. - Pro odkazové buňky použijte
int refmístoref<int>neboRef<int>. - Pro sekvence jazyka F# použijte formát přípony:
int seqmístoseq<int>.
Pro všechny ostatní typy použijte předponovou formu.
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
Pro vložené anonymní typy záznamů můžete také použít Stroustrup styl:
let f
(x: {|
x: int
y: AReallyLongTypeThatIsMuchLongerThan40Characters
|})
=
x
Formátování anotací návratového 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 funkce s n-ticemi jsou argumenty odděleny pomocí *, přičemž jsou umístěny 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 curried a tupled argumentů:
// Typical call syntax:
let SampleMixedFunction
(arg1, arg2)
(arg3, arg4, arg5)
(arg6, arg7)
(arg8, arg9, arg10) = ..
V odpovídajícím souboru podpisu jsou typy předcházené n-ticí odsazeny.
// ✔️ 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 samostatné parametry typu/omezení se vejdou, umístěte je 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 a ponechte každé omezení na jednom řádku bez ohledu na jeho délku. Umístěte > na konec posledního řádku. Odsadit 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.