Megosztás a következőn keresztül:


F#-kódformázási irányelvek

Ez a cikk útmutatást nyújt a kód úgy történő formázásához, hogy az F#-kód a következő legyen:

  • Olvashatóbb
  • A Visual Studio Code és más szerkesztők formázási eszközei által alkalmazott konvencióknak megfelelően
  • Hasonló más online kódhoz

Lásd még a kódolási konvenciókat és az összetevők tervezési irányelveit, amelyek az elnevezési konvenciókat is ismertetik.

Automatikus kódformázás

A Fantomas kódformázó az automatikus kódformázás F# közösségi szabványeszköze. Az alapértelmezett beállítások megfelelnek ennek a stíluskalauznak.

Határozottan javasoljuk ennek a kódformázásnak a használatát. Az F#-csapatokban a kódformázási specifikációkat meg kell egyeztetni és kodifikálni kell a csoportadattárba bevett kódformázási fájlban.

Általános formázási szabályok

Az F# alapértelmezés szerint jelentős üres területet használ, és a szabad terület érzékeny. Az alábbi iránymutatások útmutatást nyújtanak arra vonatkozóan, hogyan háríthatók el az ilyen jellegű kihívások.

Szóközök használata nem lapok

Ha behúzásra van szükség, szóközöket kell használnia, nem tabulátorokat. Az F#-kód nem használ lapokat, és a fordító hibaüzenetet ad, ha egy tabulátorkaraktere egy sztringkonstanson vagy megjegyzésen kívül jelenik meg.

Konzisztens behúzás használata

Behúzáskor legalább egy szóköz szükséges. A szervezet kódolási szabványokat hozhat létre a behúzáshoz használandó szóközök számának meghatározásához; Két, három vagy négy behúzási szóköz minden olyan szinten, ahol a behúzás történik, jellemző.

Behúzásonként négy szóközt ajánlunk.

Igaz, a programok behúzása szubjektív kérdés. A változatok rendben vannak, de az első szabály, amelyet követnie kell, a behúzás konzisztenciája. Válasszon egy általánosan elfogadott behúzási stílust, és használja azt szisztematikusan a kódbázisban.

Kerülje a névhosszra érzékeny formázást

Kerülje az elnevezésre érzékeny behúzást és igazítást:

// ✔️ 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 _ -> ()
    | ...

Ennek elkerülésének elsődleges okai a következők:

  • A fontos kód jobbra van helyezve
  • A tényleges kódnak kevesebb szélessége maradt
  • Az átnevezés megszakíthatja az igazítást

Kerülje a felesleges szóközt

Az F#-kódban kerülje a felesleges szóközt, kivéve az ebben a stíluskalauzban leírtakat.

// ✔️ OK
spam (ham 1)

// ❌ Not OK
spam ( ham 1 )

Megjegyzések formázása

Több dupla perjeles megjegyzés előnyben részesítve a blokkbejegyzéseket.

// 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.
*)

A megjegyzéseknek nagybetűssé kell tenni az első betűt, és jól formázott kifejezéseknek vagy mondatoknak kell lenniük.

// ✔️ A good comment.
let f x = x + 1 // Increment by one.

// ❌ two poor comments
let f x = x + 1 // plus one

Az XML-dokumentum megjegyzéseinek formázásához lásd alább a "Formázási deklarációk" című témakört.

Formázási kifejezések

Ez a szakasz a különböző típusú formázási kifejezéseket ismerteti.

Sztringkifejezések formázása

A sztringkonstansok és az interpolált sztringek csak egyetlen sorban hagyhatók, függetlenül attól, hogy mennyi a vonal.

let serviceStorageConnection =
    $"DefaultEndpointsProtocol=https;AccountName=%s{serviceStorageAccount.Name};AccountKey=%s{serviceStorageAccountKey.Value}"

A többsoros interpolált kifejezések nem támogatottak. Ehelyett kösse össze a kifejezés eredményét egy értékkel, és használja azt az interpolált sztringben.

Rekordkifejezések formázása

A rekordpéldányt zárójelbe kell tenni, és a benne lévő elválasztó vesszőket egyetlen szóközzel kell követni, például: (1, 2), (x, y, z).

// ✔️ OK
let pair = (1, 2)
let triples = [ (1, 2, 3); (11, 12, 13) ]

Általánosan elfogadott, hogy kihagyja a zárójeleket a csuplok mintaegyezésében:

// ✔️ OK
let (x, y) = z
let x, y = z

// ✔️ OK
match x, y with
| 1, _ -> 0
| x, 1 -> 0
| x, y -> 1

Gyakran elfogadott a zárójelek kihagyása is, ha a rekord egy függvény visszatérési értéke:

// ✔️ OK
let update model msg =
    match msg with
    | 1 -> model + 1, []
    | _ -> model, [ msg ]

Összefoglalva, előnyben részesítse a zárójeles tuple-példányokat, de ha a mintaegyeztetéshez vagy a visszatérési értékhez használ rekordokat, akkor érdemes elkerülni a zárójeleket.

Alkalmazáskifejezések formázása

Függvény vagy metódusalkalmazás formázásakor az argumentumok ugyanabban a sorban jelennek meg, amikor a sorszélesség lehetővé teszi:

// ✔️ OK
someFunction1 x.IngredientName x.Quantity

Hagyja ki a zárójeleket, kivéve, ha az argumentumok megkövetelik őket:

// ✔️ OK
someFunction1 x.IngredientName

// ❌ Not preferred - parentheses should be omitted unless required
someFunction1 (x.IngredientName)

// ✔️ OK - parentheses are required
someFunction1 (convertVolumeToLiter x)

Ne hagyja ki a szóközöket, ha több, curriált argumentummal rendelkező argumentumot invokált:

// ✔️ 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)

Az alapértelmezett formázási konvenciókban a rendszer szóközt ad hozzá, amikor kisbetűs függvényeket alkalmaz a csatolt vagy zárójeles argumentumokra (még akkor is, ha egyetlen argumentumot használ):

// ✔️ 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)

Az alapértelmezett formázási konvenciókban a rendszer nem ad hozzá szóközt, amikor nagybetűs metódusokat alkalmaz a tupled argumentumokra. Ennek az az oka, hogy ezeket gyakran használják folyékony programozással:

// ✔️ 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)

Előfordulhat, hogy az argumentumokat át kell adnia egy új sorban lévő függvénynek az olvashatóság függvényében, vagy mert az argumentumok listája vagy az argumentumnevek túl hosszúak. Ebben az esetben egy szint behúzása:

// ✔️ 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)

Ha a függvény egyetlen többsoros összefűző argumentumot használ, helyezze az egyes argumentumokat egy új sorba:

// ✔️ 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)

Ha az argumentumkifejezések rövidek, különítse el az argumentumokat szóközökkel, és tartsa egy sorban.

// ✔️ OK
let person = new Person(a1, a2)

// ✔️ OK
let myRegexMatch = Regex.Match(input, regex)

// ✔️ OK
let untypedRes = checker.ParseFile(file, source, opts)

Ha az argumentumkifejezések hosszúak, használjon új sorokat, és egy szint behúzásával ne a bal oldali zárójelbe.

// ✔️ 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)

Ugyanezek a szabályok akkor is érvényesek, ha csak egyetlen többsoros argumentum létezik, beleértve a többsoros sztringeket is:

// ✔️ 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" ]
)

Folyamatkifejezések formázása

Ha több sort használ, a folyamatkezelőknek |> az általuk használt kifejezések alá kell lépniük.

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

Lambda-kifejezések formázása

Ha a lambda kifejezést argumentumként használják egy többsoros kifejezésben, és más argumentumok követik, helyezze egy lambda kifejezés törzsét egy új sorra, egy szinttel behúzva:

// ✔️ OK
let printListWithOffset a list1 =
    List.iter
        (fun elem ->
             printfn $"A very long line to format the value: %d{a + elem}")
        list1

Ha a lambda argumentum egy függvényalkalmazás utolsó argumentuma, helyezze az összes argumentumot addig, amíg a nyíl ugyanarra a sorra nem kerül.

// ✔️ 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}")

A lambda-nak hasonló módon kell kezelnie.

// ✔️ OK
functionName arg1 arg2 arg3 (function
    | Choice1of2 x -> 1
    | Choice2of2 y -> 2)

Ha a lambda előtt számos sorvezető vagy többsoros argumentum van behúzva, az összes argumentumot egy szinttel kell behúzni.

// ✔️ OK
functionName
    arg1
    arg2
    arg3
    (fun arg4 ->
        bodyExpr)

// ✔️ OK
functionName
    arg1
    arg2
    arg3
    (function
     | Choice1of2 x -> 1
     | Choice2of2 y -> 2)

Ha egy lambda-kifejezés törzse több sor hosszú, érdemes megfontolni, hogy egy helyi hatókörű függvényre bontsa át.

Ha a folyamatok lambdakifejezéseket tartalmaznak, az egyes lambdakifejezések általában az utolsó argumentumok a folyamat minden szakaszában:

// ✔️ 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}")

Ha a lambda argumentumai nem férnek el egyetlen sorban, vagy maguk is többsorosak, helyezze őket a következő sorba, egy szinttel behúzva.

// ✔️ 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 ()

Aritmetikai és bináris kifejezések formázása

A bináris aritmetikai kifejezések körül mindig használjon fehér szóközt:

// ✔️ OK
let subtractThenAdd x = x - 1 + 3

Ha nem sikerül körülvenni egy bináris - operátort, bizonyos formázási lehetőségekkel kombinálva a függvényt unárisként -értelmezheti. A nem kötelező - operátorokat mindig azonnal követnie kell az általuk tagadott értéknek:

// ✔️ OK
let negate x = -x

// ❌ Not OK
let negateBad x = - x

Ha az operátor után - szóköz karaktert ad hozzá, az zavart okozhat mások számára.

Bináris operátorok elkülönítése szóközök szerint. Az infix kifejezések rendben vannak ugyanazon az oszlopon való sorbaálláshoz:

// ✔️ OK
let function1 () =
    acc +
    (someFunction
         x.IngredientName x.Quantity)

// ✔️ OK
let function1 arg1 arg2 arg3 arg4 =
    arg1 + arg2 +
    arg3 + arg4

Ez a szabály a típusok és állandó széljegyzetek mértékegységére is vonatkozik:

// ✔️ 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)> }

A következő operátorok az F# standard kódtárban vannak definiálva, és egyenértékűek definiálása helyett használhatók. Ezek az operátorok használata ajánlott, mivel a kód olvashatóbbá és idiomatikusabbá teszi a kódot. Az alábbi lista az ajánlott F#-operátorokat foglalja össze.

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

Tartomány operátorkifejezéseinek formázása

Csak akkor adjon hozzá szóközöket, .. ha az összes kifejezés nem atomi. Az egész számok és az egyszavas azonosítók atominak minősülnek.

// ✔️ 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 ]

Ezek a szabályok a szeletelésre is vonatkoznak:

// ✔️ OK
arr[0..10]
list[..^1]

Formázás, ha a kifejezések

A feltételes kifejezések behúzása az őket alkotó kifejezések méretétől és összetettségétől függ. Írja őket egy sorba, amikor:

  • cond, e1és e2 rövidek.
  • e1 és e2 nem if/then/else maguk a kifejezések.
// ✔️ OK
if cond then e1 else e2

Ha a másik kifejezés hiányzik, javasoljuk, hogy soha ne írjon a teljes kifejezést egy sorba. Ennek célja, hogy megkülönböztesse az imperatív kódot a funkcionálistól.

// ✔️ OK
if a then
    ()

// ❌ Not OK, code formatters will reformat to the above by default
if a then ()

Ha bármelyik kifejezés többsoros, minden feltételes ágnak többsorosnak kell lennie.

// ✔️ OK
if cond then
    let e1 = something()
    e1
else
    e2
    
// ❌ Not OK
if cond then
    let e1 = something()
    e1
else e2

Több feltételes elif else feltétel ugyanazon a hatókörön belül van behúzva, mint if amikor az egysoros if/then/else kifejezések szabályait követik.

// ✔️ OK
if cond1 then e1
elif cond2 then e2
elif cond3 then e3
else e4

Ha bármelyik feltétel vagy kifejezés többsoros, a teljes if/then/else kifejezés többsoros:

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

Ha egy feltétel többsoros vagy meghaladja az egysoros alapértelmezett tűréshatárt, a feltételkifejezésnek egy behúzást és egy új sort kell használnia. A if hosszú feltételkifejezés beágyazásakor a kulcsszónak és then a kulcsszónak egymáshoz kell igazodnia.

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

Azonban jobb stílus a hosszú feltételek újrabontása egy let kötésre vagy különálló függvényre:

// ✔️ OK
let performAction =
    complexExpression a b && env.IsDevelopment()
    || someFunctionToCall
        aVeryLongParameterNameOne
        aVeryLongParameterNameTwo
        aVeryLongParameterNameThree

if performAction then
    e1
else
    e2

Egyesítő kis- és nagybetűk formázása

A diszkriminált uniós esetek alkalmazása ugyanazokat a szabályokat követi, mint a függvény- és metódusalkalmazások. Ez azt jelzi, hogy mivel a név nagybetűs, a kódformátum-formázók eltávolítanak egy szóközt a rekord előtt:

// ✔️ OK
let opt = Some("A", 1)

// OK, but code formatters will remove the space
let opt = Some ("A", 1)

A függvényalkalmazásokhoz hasonlóan a több sorra osztott szerkezeteknek is behúzást kell használniuk:

// ✔️ OK
let tree1 =
    BinaryNode(
        BinaryNode (BinaryValue 1, BinaryValue 2),
        BinaryNode (BinaryValue 3, BinaryValue 4)
    )

Lista és tömbkifejezések formázása

Írás x :: l szóközökkel az :: operátor körül (:: egy infix operátor, ezért szóközökkel körülvéve).

Az egy sorban deklarált listáknak és tömböknek szóközzel kell rendelkezniük a nyitó zárójel és a záró zárójel előtt:

// ✔️ OK
let xs = [ 1; 2; 3 ]

// ✔️ OK
let ys = [| 1; 2; 3; |]

Mindig használjon legalább egy szóközt két különböző kapcsos operátor között. Például hagyjon szóközt az a [ és a {között.

// ✔️ 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 }]

Ugyanez az útmutató vonatkozik a listákra vagy a csuplok tömbjeire is.

A több sorra osztott listák és tömbök a rekordokhoz hasonló szabályt követnek:

// ✔️ 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 |] |]

A rekordokhoz hasonlóan a nyitó és záró zárójelek saját sorban való deklarálása megkönnyíti a kód mozgatását és a függvényekbe való beásást:

// ✔️ 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 |] 
    |]

Ha egy lista- vagy tömbkifejezés a kötés jobb oldala, érdemes lehet stílust használnia Stroustrup :

// ✔️ 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 |] 
|]

Ha azonban egy lista- vagy tömbkifejezés nem a kötés jobb oldala, például ha egy másik listában vagy tömbben van, ha a belső kifejezésnek több sorra kell kiterjednie, a zárójeleknek a saját vonalaikra kell lépniük:

// ✔️ 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
] ]

Ugyanez a szabály vonatkozik a tömbök/listák rekordtípusaira:

// ✔️ 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
} ]

Tömbök és listák programozott létrehozásakor előnyben kell részesedni -> do ... yield , ha egy érték mindig létre van hozva:

// ✔️ 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 ]

Az F# régebbi verzióira van szükség yield olyan helyzetekben, amikor az adatok feltételesen hozhatók létre, vagy egymást követő kifejezések kiértékelhetők. Inkább kihagyja ezeket a yield kulcsszavakat, hacsak nem kell lefordítania egy régebbi F#-nyelvi verzióval:

// ✔️ 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"
    ]

Bizonyos esetekben do...yield segíthet az olvashatóságban. Ezeket az eseteket, bár szubjektív, figyelembe kell venni.

Rekordkifejezések formázása

A rövid rekordok egy sorban írhatók:

// ✔️ OK
let point = { X = 1.0; Y = 0.0 }

A hosszabb rekordoknak új sorokat kell használniuk a címkékhez:

// ✔️ OK
let rainbow =
    { Boss = "Jeffrey"
      Lackeys = ["Zippy"; "George"; "Bungle"] }

Többsoros zárójel formázási stílusai

A több sorra kiterjedő rekordok esetében három gyakran használt formázási stílus létezik: Cramped, Alignedés Stroustrup. Az Cramped F#-kód alapértelmezett stílusa a stílus, mivel általában olyan stílusokat részesít előnyben, amelyek lehetővé teszik a fordító számára a kód egyszerű elemzését. Mind Aligned a Stroustrup stílusok lehetővé teszik a tagok egyszerűbb átrendezését, ami a kód könnyebb újrabontását eredményezheti, azzal a hátránysal, hogy bizonyos helyzetekben kissé részletesebb kódra lehet szükség.

  • Cramped: Az előzményszabvány és az alapértelmezett F# rekordformátum. A nyitó zárójelek ugyanazon a sorban haladnak, mint az első tag, a záró zárójel ugyanazon a sorban, mint az utolsó tag.

    let rainbow = 
        { Boss1 = "Jeffrey"
          Boss2 = "Jeffrey"
          Boss3 = "Jeffrey"
          Lackeys = [ "Zippy"; "George"; "Bungle" ] }
    
  • Aligned: A szögletes zárójelek mindegyike saját vonalat kap, ugyanarra az oszlopra igazítva.

    let rainbow =
        {
            Boss1 = "Jeffrey"
            Boss2 = "Jeffrey"
            Boss3 = "Jeffrey"
            Lackeys = ["Zippy"; "George"; "Bungle"]
        }
    
  • Stroustrup: A nyitó zárójel ugyanazon a vonalon halad, mint a kötés, a záró zárójel saját vonalat kap.

    let rainbow = {
        Boss1 = "Jeffrey"
        Boss2 = "Jeffrey"
        Boss3 = "Jeffrey"
        Lackeys = [ "Zippy"; "George"; "Bungle" ]
    }
    

A lista- és tömbelemekre ugyanazok a formázási stílusszabályok vonatkoznak.

Rekordkifejezések másolása és frissítése formázása

A másolási és frissítési rekordkifejezések továbbra is rekordnak számítanak, ezért hasonló irányelvek érvényesek.

A rövid kifejezések egy sorban elférnek:

// ✔️ OK
let point2 = { point with X = 1; Y = 2 }

A hosszabb kifejezéseknek új sorokat kell használniuk, és a fent említett konvenciók egyikén alapuló formátumot kell használniuk:

// ✔️ 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 = ""
            }
}

Megjegyzés: Ha stílust használ Stroustrup a másolási és frissítési kifejezésekhez, a másolt rekordnéven kívül be kell húznia a tagokat:

// ✔️ 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" 
}

Formázási minta egyeztetése

Használjon behúzás nélküli egyezés minden záradékához egy-egyezést | . Ha a kifejezés rövid, akkor érdemes lehet egyetlen sort használni, ha az egyes alkifejezések is egyszerűek.

// ✔️ 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"

Ha a mintaillesztési nyíl jobb oldalán lévő kifejezés túl nagy, mozgassa a következő sorba, és húzza egy lépéssel a nyilat.match/|

// ✔️ OK
match lam with
| Var v -> 1
| Abs(x, body) ->
    1 + sizeLambda body
| App(lam1, lam2) ->
    sizeLambda lam1 + sizeLambda lam2

Ha egy egyező kifejezés többsoros, vagy meghaladja az egysoros alapértelmezett tűréshatárt, a nagy feltételekhez hasonlóan az egyező kifejezésnek egy behúzást és egy új sort kell használnia. A match hosszú egyezés kifejezés beágyazásakor a kulcsszónak with igazodnia kell.

// ✔️ OK, but better to refactor, see below
match
    complexExpression a b && env.IsDevelopment()
    || someFunctionToCall
        aVeryLongParameterNameOne
        aVeryLongParameterNameTwo
        aVeryLongParameterNameThree 
with
| X y -> y
| _ -> 0

Azonban jobb stílus a hosszú egyezések kifejezéseinek újrabontása egy let kötéssel vagy különálló függvénnyel:

// ✔️ OK
let performAction =
    complexExpression a b && env.IsDevelopment()
    || someFunctionToCall
        aVeryLongParameterNameOne
        aVeryLongParameterNameTwo
        aVeryLongParameterNameThree

match performAction with
| X y -> y
| _ -> 0

Kerülni kell a mintaegyezés nyílainak igazítását.

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

A kulcsszó function használatával bevezetett mintaegyeztetésnek egy szintet kell behúznia az előző sor elejéről:

// ✔️ OK
lambdaList
|> List.map (function
    | Abs(x, body) -> 1 + sizeLambda 0 body
    | App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2
    | Var v -> 1)

A függvények function let let rec használatát általában el kell kerülni matcha . Használat esetén a mintaszabályoknak a következő kulcsszóhoz functionkell igazodniuk:

// ✔️ 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ázási kísérlet/kifejezésekkel

A kivételtípushoz tartozó mintamegfeleltetéseket a kivételtípussal megegyező szinten withkell behúzni.

// ✔️ 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"

Adjon hozzá egy-egy | záradékot, kivéve, ha csak egyetlen záradék van:

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

Elnevezett argumentumok formázása

A névvel ellátott argumentumoknak szóközöknek kell körülvennie a következőt =:

// ✔️ 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)

Ha a mintaegyeztetés diszkriminált egyesítésekkel történik, a névvel ellátott minták formázása hasonlóan történik, például.

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

Mutációs kifejezések formázása

A mutációs location <- expr kifejezések általában egy sorban vannak formázva. Ha többsoros formázásra van szükség, helyezze a jobb oldali kifejezést egy új sorra.

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

Objektumkifejezések formázása

Az objektumkifejezések tagjainak egy szinttel kell igazodniuk member a behúzáshoz.

// ✔️ 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) }

A stílust is előnyben részesítheti Stroustrup :

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)
}

Az üres típusdefiníciók egy sorban formázhatók:

type AnEmptyType = class end

A választott oldalszélességtől = class end függetlenül mindig ugyanabban a sorban kell lennie.

Index-/szeletkifejezések formázása

Az indexkifejezések nem tartalmazhatnak szóközöket a nyitó és záró zárójelek körül.

// ✔️ OK
let v = expr[idx]
let y = myList[0..1]

// ❌ Not OK
let v = expr[ idx ]
let y = myList[ 0 .. 1 ]

Ez a régebbi expr.[idx] szintaxisra is vonatkozik.

// ✔️ OK
let v = expr.[idx]
let y = myList.[0..1]

// ❌ Not OK
let v = expr.[ idx ]
let y = myList.[ 0 .. 1 ]

Idézett kifejezések formázása

A határolójeleket (<@, , @>, <@@) @@>külön sorokba kell helyezni, ha az idézett kifejezés többsoros kifejezés.

// ✔️ OK
<@
    let f x = x + 10
    f 20
@>

// ❌ Not OK
<@ let f x = x + 10
   f 20
@>

Az egysoros kifejezésekben a határolójeleket ugyanazon a vonalon kell elhelyezni, mint maga a kifejezés.

// ✔️ OK
<@ 1 + 1 @>

// ❌ Not OK
<@
    1 + 1
@>

Láncolt kifejezések formázása

Ha a láncolt kifejezések (a .függvényalkalmazások összefonódnak ) hosszúak, helyezze az egyes alkalmazáshívásokat a következő sorba. A lánc későbbi hivatkozásainak behúzása egy szinttel a bevezető hivatkozás után.

// ✔️ OK
Host
    .CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())

// ✔️ OK
Cli
    .Wrap("git")
    .WithArguments(arguments)
    .WithWorkingDirectory(__SOURCE_DIRECTORY__)
    .ExecuteBufferedAsync()
    .Task

A vezető hivatkozás több hivatkozásból állhat, ha egyszerű azonosítók. Például egy teljesen minősített névtér hozzáadása.

// ✔️ OK
Microsoft.Extensions.Hosting.Host
    .CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>())

Az ezt követő hivatkozásoknak egyszerű azonosítókat is tartalmazniuk kell.

// ✔️ 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))

Ha a függvényalkalmazásban lévő argumentumok nem férnek el a sor többi részén, minden argumentumot a következő sorba kell helyezni.

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

A lambda argumentumoknak a függvényalkalmazáson belül a nyitóval (megegyező sorban kell kezdődnie.

// ✔️ OK
builder
    .WithEnvironment()
    .WithLogger(fun loggerConfiguration ->
        // ...
        ())

// ❌ Not OK, formatting tools will reformat to the above
builder
    .WithEnvironment()
    .WithLogger(
        fun loggerConfiguration ->
        // ...
        ())

Formázási deklarációk

Ez a szakasz a különböző típusú formázási deklarációkat ismerteti.

Üres sorok hozzáadása deklarációk között

Különítse el a legfelső szintű függvényeket és osztálydefiníciókat egyetlen üres sortal. Példa:

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

Ha egy szerkezethez XML-dokumentum megjegyzései vannak, adjon hozzá egy üres sort a megjegyzés elé.

// ✔️ 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ázási let és tagdeklarációk

Formázás let és member deklarációk esetén a kötés jobb oldala általában vagy egy sorra, vagy (ha túl hosszú) egy szinten behúzott új sorra kerül.

A következő példák például megfelelőek:

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

Ezek nem megfelelőek:

// ❌ Not OK, code formatters will reformat to the above by default
let a = """
foobar, long string
"""

let d = while f do
    printfn "%A" x

A rekordtípus-példányok a szögletes zárójeleket is elhelyezhetik a saját vonalaikon:

// ✔️ OK
let bilbo =
    { 
        Name = "Bilbo"
        Age = 111
        Region = "The Shire" 
    }

A stílust is előnyben részesítheti Stroustrup , ha a megnyitás { a kötés nevével azonos vonalon történik:

// ✔️ OK
let bilbo = {
    Name = "Bilbo"
    Age = 111
    Region = "The Shire"
}

Különítse el a tagokat egyetlen üres sortal és dokumentummal, és adjon hozzá egy dokumentációs megjegyzést:

// ✔️ OK

/// This is a thing
type ThisThing(value: int) =

    /// Gets the value
    member _.Value = value

    /// Returns twice the value
    member _.TwiceValue() = value*2

További üres sorok használhatók (takarékosan) a kapcsolódó függvények csoportjainak elkülönítéséhez. Üres sorokat hagyhat ki egy csomó kapcsolódó egysoros (például próbabábu-implementációk) között. Használjon üres sorokat a függvényekben, takarékosan, a logikai szakaszok jelzéséhez.

Formázási függvény és tagargumentumok

Függvények definiálásakor használjon üres szóközt az egyes argumentumok köré.

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

Ha hosszú függvénydefinícióval rendelkezik, helyezze a paramétereket az új sorokra, és a behúzásukkal egyezzen a következő paraméter behúzási szintjével.

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

Ez a tagokra, konstruktorokra és paraméterekre is vonatkozik, amelyek a következő műveletet használják:

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

Ha a paraméterek curriáltak, helyezze a = karaktert a visszatérési típussal együtt egy új sorba:

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

Így elkerülheti a túl hosszú sorokat (abban az esetben, ha a visszatérési típus hosszú névvel rendelkezik), és a paraméterek hozzáadásakor kevesebb sorsérülést okoz.

Formázási operátor deklarációi

Ha szeretné, használjon üres területet egy operátordefiníció körül:

// ✔️ OK
let ( !> ) x f = f x

// ✔️ OK
let (!>) x f = f x

Minden olyan egyéni operátor esetében, amely egynél több karakterből * áll, a fordító kétértelműségének elkerülése érdekében fehér szóközt kell hozzáadnia a definíció elejéhez. Ezért azt javasoljuk, hogy egyszerűen vegye körül az összes operátor definícióját egyetlen szóköz karakterrel.

Rekorddeklarációk formázása

Rekorddeklarációk esetén alapértelmezés szerint négy szóközzel kell behúzni a { típusdefiníciót, ugyanabban a sorban elindítani a címkelistát, és a tagokat (ha vannak) a { jogkivonathoz igazítani:

// ✔️ OK
type PostalAddress =
    { Address: string
      City: string
      Zip: string }

Az is gyakori, hogy inkább szögletes zárójeleket helyeznek a saját vonalukra, és a címkéket további négy szóköz húzja be:

// ✔️ OK
type PostalAddress =
    { 
        Address: string
        City: string
        Zip: string 
    }

A típusdefiníció (Stroustrupstílus) első sorának végén is elhelyezheti { a következőt:

// ✔️ OK
type PostalAddress = {
    Address: string
    City: string
    Zip: string
}

Ha további tagokra van szükség, ne használja with/end , ha lehetséges:

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

Ez alól a stílusszabály alól kivételt képeznek a rekordok stílus szerinti formázása Stroustrup . Ebben az esetben a fordítószabályok miatt a with kulcsszóra akkor van szükség, ha egy felületet szeretne implementálni, vagy további tagokat szeretne hozzáadni:

// ✔️ 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}"

Ha xml-dokumentációt ad hozzá a rekordmezőkhöz, Aligned vagy Stroustrup a stílust részesíti előnyben, és további szóközt kell hozzáadni a tagok között:

// ❌ 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}"

A nyitó jogkivonat új sorra helyezése és a záró jogkivonat új sorra helyezése akkor előnyös, ha felületi implementációkat vagy tagokat deklarál a rekordon:

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

Ugyanezek a szabályok vonatkoznak a névtelen rekordtípus-aliasokra is.

Diszkriminált egyesítő deklarációk formázása

Hátrányos megkülönböztetés esetén a típusdefiníciót négy szóközzel kell behúzni | :

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

Ha egyetlen rövid egyesítés van, kihagyhatja a vezetőt |.

// ✔️ OK
type Address = Address of string

Hosszabb vagy többsoros egyesítés esetén tartsa az | egyesítő mezőket egy új sorban, és helyezze el az egyes sorok végén lévő elválasztást * .

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

Dokumentációs megjegyzések hozzáadásakor minden megjegyzés előtt /// használjon egy üres sort.

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

Literális deklarációk formázása

Az attribútumot használó F#-literáloknak az Literal attribútumot a saját sorukra kell helyeznie, és PascalCase-elnevezést kell használniuk:

// ✔️ OK

[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "/" + __SOURCE_FILE__

[<Literal>]
let MyUrl = "www.mywebsitethatiamworkingwith.com"

Ne helyezze az attribútumot ugyanabba a sorba, mint az érték.

Formázási modul deklarációi

A helyi modul kódját be kell húzni a modulhoz képest, de a legfelső szintű modul kódját nem szabad behúzni. A névtérelemeket nem kell behúzni.

// ✔️ 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ázási do deklarációk

Típusdeklarációkban, moduldeklarációkban és számítási kifejezésekben a mellékhatás-műveletek használata do vagy do! néha szükséges. Ha ezek több sorra is kiterjednek, a behúzással és egy új vonallal konzisztenssé kell tenni a behúzást let/let!. Íme egy példa az osztályban való használatra do :

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

Íme egy példa do! két behúzási szóköz használatával (mivel do! négy behúzási szóköz használatakor véletlenül nincs különbség a megközelítések között):

// ✔️ 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
}

Számítási kifejezések formázása

A számítási kifejezések egyéni műveleteinek létrehozásakor a CamelCase elnevezés használata javasolt:

// ✔️ 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
    }

A modellezett tartománynak végső soron az elnevezési konvenciót kell vezetnie. Ha egy másik konvenciót használ, akkor inkább ezt a konvenciót kell használni.

Ha egy kifejezés visszatérési értéke számítási kifejezés, inkább a számítási kifejezés kulcsszónevét helyezze a saját sorára:

// ✔️ OK
let foo () = 
    async {
        let! value = getValue()
        do! somethingElse()
        return! anotherOperation value 
    }

A számítási kifejezést a kötés nevével megegyező sorba is helyezheti:

// ✔️ OK
let foo () = async {
    let! value = getValue()
    do! somethingElse()
    return! anotherOperation value 
}

Bármelyiket is szeretné, törekedjen arra, hogy konzisztens maradjon a kódbázisban. A formázók lehetővé tehetik, hogy ezt a beállítást konzisztensként adja meg.

Formázási típusok és szövegjegyzetek

Ez a szakasz a formázási típusokat és a szövegjegyzeteket ismerteti. Ide tartoznak a bővítményt tartalmazó aláírásfájlok formázása .fsi .

Típusok esetén előnyben részesítse az általános (Foo<T>) előtagszintaxisát néhány konkrét kivétellel

Az F# lehetővé teszi az általános típusok írásának postfix stílusát (például int list) és az előtagstílust (például list<int>). A postfix stílus csak egyetlen típusargumentummal használható. Mindig a .NET stílust részesíti előnyben, kivéve öt konkrét típust:

  1. F#-listák esetén használja a postfix űrlapot ahelyett, int list hogy list<int>.
  2. Az F# beállításainál használja a postfix űrlapot ahelyett, int option hogy option<int>.
  3. Az F# értékbeállításaihoz használja a postfix űrlapot ahelyett, int voption hogy voption<int>a következőt használjuk.
  4. F#-tömbök esetén használja a postfix űrlapot: int array helyett vagy int[]helyettarray<int>.
  5. Hivatkozáscellák esetén használja int ref ref<int> ahelyett vagy Ref<int>.

Minden más típushoz használja az előtag űrlapot.

Formázási függvénytípusok

Egy függvény aláírásának meghatározásakor használjon fehér szóközt a -> szimbólum körül:

// ✔️ OK
type MyFun = int -> int -> string

// ❌ Not OK
type MyFunBad = int->int->string

Formázási érték és argumentumtípus-széljegyzetek

Ha típusjegyzetekkel definiál értékeket vagy argumentumokat, használjon üres szóközt a : szimbólum után, de előtte ne:

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

Többsoros típusú széljegyzetek formázása

Ha egy szöveg széljegyzete hosszú vagy többsoros, helyezze őket a következő sorba, egy szinttel behúzva.

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

Beágyazott névtelen rekordtípusok esetén a stílust is használhatja Stroustrup :

let f
    (x: {|
        x: int
        y: AReallyLongTypeThatIsMuchLongerThan40Characters
     |})
    =
    x

Formázás– visszatérési típusú széljegyzetek

A függvényben vagy tag típusjegyzetekben használjon üres szóközt a : szimbólum előtt és után:

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

Formázási típusok az aláírásokban

Ha teljes függvénytípusokat ír aláírásokban, néha szükség van az argumentumok több sorra való felosztására. A visszatérési típus mindig be van húzva.

A tupled függvények argumentumait az egyes sorok végén elhelyezett argumentumok választják el *egymástól.

Vegyünk például egy függvényt a következő implementációval:

let SampleTupledFunction(arg1, arg2, arg3, arg4) = ...

A megfelelő aláírásfájlban (.fsi kiterjesztésben) a függvény a következőképpen formázható, ha többsoros formázásra van szükség:

// ✔️ OK
val SampleTupledFunction:
    arg1: string *
    arg2: string *
    arg3: int *
    arg4: int ->
        int list

Hasonlóképpen fontolja meg a curried függvényt is:

let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...

A megfelelő aláírási fájlban a -> sorokat az egyes sorok végén helyezik el:

// ✔️ OK
val SampleCurriedFunction:
    arg1: string ->
    arg2: string ->
    arg3: int ->
    arg4: int ->
        int list

Hasonlóképpen fontolja meg egy olyan függvényt, amely a curried és a tupled argumentumok kombinációját használja:

// Typical call syntax:
let SampleMixedFunction
        (arg1, arg2)
        (arg3, arg4, arg5)
        (arg6, arg7)
        (arg8, arg9, arg10) = ..

A megfelelő aláírási fájlban a tuple előtt lévő típusok be vannak húzva

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

Ugyanezek a szabályok vonatkoznak a típusaadékokban lévő tagokra is:

type SampleTypeName =
    member ResolveDependencies:
        arg1: string *
        arg2: string ->
            string

Explicit általános típusú argumentumok és megkötések formázása

Az alábbi irányelvek a függvénydefiníciókra, tagdefiníciókra, típusdefiníciókra és függvényalkalmazásokra vonatkoznak.

Ha nem túl hosszú, tartsa meg az általános típusargumentumokat és korlátozásokat egy sorban:

// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison> param =
    // function body

Ha az általános típusargumentumok/kényszerek és függvényparaméterek nem felelnek meg, de a típusparaméterek/kényszerek önmagukban igen, helyezze a paramétereket az új sorokra:

// ✔️ OK
let f<'T1, 'T2 when 'T1: equality and 'T2: comparison>
    param
    =
    // function body

Ha a típusparaméterek vagy kényszerek túl hosszúak, bontsa meg és igazítsa őket az alább látható módon. A típusparaméterek listájának megőrzése a függvényével megegyező sorban, a hosszától függetlenül. Korlátozások esetén helyezze when az első sorra, és tartsa az egyes kényszereket egyetlen sorban, függetlenül annak hosszától. Helyezze > az utolsó sor végére. A kényszerek behúzása egy szinttel.

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

Ha a típusparaméterek/megkötések fel vannak bontva, de nincsenek normál függvényparaméterek, helyezze az = új sorra, függetlenül attól, hogy:

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

A függvényalkalmazásokra ugyanazok a szabályok vonatkoznak:

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

Formázás öröklése

Az alaposztály-konstruktor argumentumai megjelennek a záradék argumentumlistájában inherit . Helyezze a inherit záradékot egy új sorra, egy szinttel behúzva.

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)

Ha a konstruktor hosszú vagy többsoros, helyezze őket a következő sorba, egy szinttel behúzva.
Formázza ezt a többsoros konstruktort a többsoros függvényalkalmazások szabályai szerint.

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
        """)

Az elsődleges konstruktor formázása

Az alapértelmezett formázási konvenciókban nincs hely a típusnév és az elsődleges konstruktor zárójelei között.

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

Több konstruktor

Ha a inherit záradék egy rekord része, helyezze ugyanazon a sorban, ha rövid. És tegye a következő sorba, egy szinttel behúzva, ha hosszú vagy többsoros.

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 }

Formázási attribútumok

Az attribútumok egy szerkezet fölé kerülnek:

// ✔️ OK
[<SomeAttribute>]
type MyClass() = ...

// ✔️ OK
[<RequireQualifiedAccess>]
module M =
    let f x = x

// ✔️ OK
[<Struct>]
type MyRecord =
    { Label1: int
      Label2: string }

Minden XML-dokumentáció után kell menniük:

// ✔️ OK

/// Module with some things in it.
[<RequireQualifiedAccess>]
module M =
    let f x = x

Paraméterek formázási attribútumai

Az attribútumok paraméterekre is elhelyezhetők. Ebben az esetben helyezze a paraméter és a név elé a következő sort:

// ✔️ OK - defines a class that takes an optional value as input defaulting to false.
type C() =
    member _.M([<Optional; DefaultParameterValue(false)>] doSomething: bool)

Több attribútum formázása

Ha több attribútumot alkalmaz egy olyan szerkezetre, amely nem paraméter, helyezze az egyes attribútumokat egy külön sorba:

// ✔️ OK

[<Struct>]
[<IsByRefLike>]
type MyRecord =
    { Label1: int
      Label2: string }

Ha paraméterre van alkalmazva, helyezze az attribútumokat ugyanarra a sorra, és különítse el őket egy ; elválasztóval.

Köszönetnyilvánítás

Ezek az irányelvek Anh-Dung Phan F#-formázási konvenciókra vonatkozó átfogó útmutatóján alapulnak.