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ž:

  • conde2 a e1jsou krátké.
  • e1 a e2 nejsou if/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 elifif 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, 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#. 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 functionlet 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ý výraz.

// ✔️ 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ů:

  1. Pro seznamy jazyka F# použijte formát přípony, int list nikoli list<int>.
  2. Pro možnosti jazyka F# použijte formát přípony, int option nikoli option<int>.
  3. Pro možnosti hodnot jazyka F# použijte formát přípony, int voption nikoli voption<int>.
  4. Pro pole jazyka F# použijte formát přípony, int array nikoli array<int> nebo int[].
  5. Pro odkazové buňky použijte int ref místo ref<int> nebo Ref<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.