Share via


Richtlijnen voor het opmaken van F#-code

Dit artikel bevat richtlijnen voor het opmaken van uw code, zodat uw F#-code:

  • Beter leesbaar
  • In overeenstemming met conventies die worden toegepast door opmaakprogramma's in Visual Studio Code en andere editors
  • Vergelijkbaar met andere code online

Zie ook codeconventies en ontwerprichtlijnen voor onderdelen, die ook betrekking hebben op naamconventies.

Automatische codeopmaak

De Fantomas-code formatter is het standaardhulpprogramma van de F#-community voor het automatisch opmaken van code. De standaardinstellingen komen overeen met deze stijlgids.

We raden u ten zeerste aan om deze code-formatter te gebruiken. Binnen F#-teams moeten specificaties voor codeopmaak worden overeengekomen en gecodificeerd in termen van een overeengekomen instellingenbestand voor de code-formatter die is ingecheckt in de teamopslagplaats.

Algemene regels voor opmaak

F# maakt standaard gebruik van aanzienlijke witruimte en is gevoelig voor witruimte. De volgende richtlijnen zijn bedoeld om richtlijnen te bieden voor het oplossen van enkele uitdagingen die dit kan opleggen.

Spaties niet-tabbladen gebruiken

Wanneer inspringing is vereist, moet u spaties gebruiken, niet tabs. F#-code gebruikt geen tabbladen en de compiler geeft een fout als er een tabteken wordt aangetroffen buiten een letterlijke tekenreeks of opmerking.

Consistente inspringing gebruiken

Bij het inspringen is ten minste één spatie vereist. Uw organisatie kan coderingsstandaarden maken om het aantal spaties op te geven dat moet worden gebruikt voor inspringing; twee, drie of vier spaties van inspringing op elk niveau waar inspringing plaatsvindt, is typisch.

We raden vier spaties per inspringing aan.

Dat gezegd hebbende, inspringing van programma's is een subjectieve kwestie. Variaties zijn ok, maar de eerste regel die u moet volgen, is consistentie van inspringing. Kies een algemeen geaccepteerde inspringingsstijl en gebruik deze systematisch in uw codebasis.

Opmaak vermijden die gevoelig is voor naamlengte

Probeer inspringing en uitlijning te voorkomen die gevoelig is voor naamgeving:

// ✔️ OK
let myLongValueName =
    someExpression
    |> anotherExpression

// ❌ Not OK
let myLongValueName = someExpression
                      |> anotherExpression

// ✔️ OK
let myOtherVeryLongValueName =
    match
        someVeryLongExpressionWithManyParameters
            parameter1
            parameter2
            parameter3
        with
    | Some _ -> ()
    | ...

// ❌ Not OK
let myOtherVeryLongValueName =
    match someVeryLongExpressionWithManyParameters parameter1
                                                   parameter2
                                                   parameter3 with
    | Some _ -> ()
    | ...

// ❌ Still Not OK
let myOtherVeryLongValueName =
    match someVeryLongExpressionWithManyParameters
              parameter1
              parameter2
              parameter3 with
    | Some _ -> ()
    | ...

De belangrijkste redenen om dit te voorkomen zijn:

  • Belangrijke code wordt ver naar rechts verplaatst
  • Er is minder breedte over voor de werkelijke code
  • Als u de naam wijzigt, kan de uitlijning worden verbroken

Vermijd overbodige witruimte

Vermijd overbodige witruimte in F#-code, behalve waar beschreven in deze stijlgids.

// ✔️ OK
spam (ham 1)

// ❌ Not OK
spam ( ham 1 )

Opmerkingen opmaken

Geef de voorkeur aan meerdere dubbele slash-opmerkingen boven blokopmerkingen.

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

Opmerkingen moeten de eerste letter hoofdletters en goed gevormde zinnen of zinnen zijn.

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

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

Zie 'Opmaakdeclaraties' hieronder voor het opmaken van XML-documentenopmerkingen.

Opmaakexpressies

In deze sectie worden opmaakexpressies van verschillende soorten besproken.

Tekenreeksexpressies opmaken

Letterlijke tekenreeksen en geïnterpoleerde tekenreeksen kunnen gewoon op één regel worden achtergelaten, ongeacht hoe lang de lijn is.

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

Geïnterpoleerde expressies met meerdere regels worden afgeraden. Bind in plaats daarvan het expressieresultaat aan een waarde en gebruik dit in de geïnterpoleerde tekenreeks.

Tuple-expressies opmaken

Een tuple-instantiëring moet tussen haakjes worden geplaatst en de scheidingstekens erin moeten worden gevolgd door één spatie, bijvoorbeeld: (1, 2), (x, y, z).

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

Het wordt meestal geaccepteerd om haakjes weg te laten in patroonkoppeling van tuples:

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

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

Het wordt ook vaak geaccepteerd om haakjes weg te laten als de tuple de retourwaarde van een functie is:

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

Kortom, voorkeur voor haakjes tuple-instantiëringen, maar wanneer u tuples gebruikt voor patroonkoppeling of een retourwaarde, wordt het beschouwd als prima om haakjes te voorkomen.

Toepassingsexpressies opmaken

Bij het opmaken van een functie of methodetoepassing worden argumenten op dezelfde regel opgegeven wanneer regelbreedte het volgende toestaat:

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

Laat haakjes weg, tenzij argumenten deze vereisen:

// ✔️ OK
someFunction1 x.IngredientName

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

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

Laat geen spaties weg bij het aanroepen van meerdere curried argumenten:

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

In standaardopmaakconventies wordt een spatie toegevoegd bij het toepassen van kleine letters op tupled- of haakjes argumenten (zelfs wanneer één argument wordt gebruikt):

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

In standaardopmaakconventies wordt geen spatie toegevoegd bij het toepassen van hoofdletters op tupled-argumenten. Dit komt doordat deze vaak worden gebruikt met vloeiend programmeren:

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

Mogelijk moet u argumenten doorgeven aan een functie op een nieuwe regel als een kwestie van leesbaarheid of omdat de lijst met argumenten of de argumentnamen te lang zijn. In dat geval kunt u één niveau laten inspringen:

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

Wanneer de functie één tupled argument met meerdere regels gebruikt, plaatst u elk argument op een nieuwe regel:

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

Als argumentexpressies kort zijn, scheidt u argumenten met spaties en houdt u deze op één regel.

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

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

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

Als argumentexpressies lang zijn, gebruikt u nieuwe regels en inspringt u één niveau in plaats van tussen haakjes links te laten inspringen.

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

Dezelfde regels zijn ook van toepassing als er slechts één argument voor meerdere regels is, inclusief tekenreeksen met meerdere regels:

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

Pijplijnexpressies opmaken

Wanneer u meerdere regels gebruikt, moeten pijplijnoperators |> onder de expressies gaan waarop ze werken.

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

Wanneer een lambda-expressie wordt gebruikt als argument in een expressie met meerdere regels en wordt gevolgd door andere argumenten, plaatst u de hoofdtekst van een lambda-expressie op een nieuwe regel, ingesprongen met één niveau:

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

Als het lambda-argument het laatste argument in een functietoepassing is, plaatst u alle argumenten tot de pijl op dezelfde regel.

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

Behandel match lambda's op een vergelijkbare manier.

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

Als er veel voorloop- of meerdere regelargumenten zijn voordat de lambda alle argumenten met één niveau inspringt.

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

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

Als de hoofdtekst van een lambda-expressie meerdere regels lang is, kunt u overwegen deze te herstructureren in een lokaal bereikfunctie.

Wanneer pijplijnen lambda-expressies bevatten, is elke lambda-expressie doorgaans het laatste argument in elke fase van de pijplijn:

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

Als de argumenten van een lambda niet op één regel passen of meerdere regels zelf zijn, plaatst u deze op de volgende regel, ingesprongen met één niveau.

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

Rekenkundige en binaire expressies opmaken

Gebruik altijd witruimte rond binaire rekenkundige expressies:

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

Als u een binaire - operator niet omsluit, kan dit, in combinatie met bepaalde opmaakkeuzen, ertoe leiden dat deze wordt geïnterpreteerd als een unaire -operator. Unary-operators - moeten altijd onmiddellijk worden gevolgd door de waarde die ze ontzegen:

// ✔️ OK
let negate x = -x

// ❌ Not OK
let negateBad x = - x

Het toevoegen van een spatieteken na de - operator kan leiden tot verwarring voor anderen.

Scheid binaire operatoren op spaties. Infix-expressies zijn in orde om op dezelfde kolom te komen:

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

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

Deze regel is ook van toepassing op maateenheden in typen en constante aantekeningen:

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

De volgende operators worden gedefinieerd in de F#-standaardbibliotheek en moeten worden gebruikt in plaats van equivalenten te definiëren. Het gebruik van deze operators wordt aanbevolen omdat code meestal beter leesbaar en idiomatisch wordt. De volgende lijst bevat een overzicht van de aanbevolen F#-operators.

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

Operatorexpressies voor opmaakbereik

Voeg alleen spaties toe rond het .. moment dat alle expressies niet atomisch zijn. Gehele getallen en id's van één woord worden beschouwd als atomisch.

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

Deze regels zijn ook van toepassing op segmentering:

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

Opmaak als expressies

De inspringing van voorwaardelijke voorwaarden is afhankelijk van de grootte en complexiteit van de expressies waaruit deze bestaat. Schrijf ze op één regel wanneer:

  • cond, e1en e2 zijn kort.
  • e1 en e2 zijn geen if/then/else expressies zelf.
// ✔️ OK
if cond then e1 else e2

Als de else-expressie afwezig is, is het raadzaam om nooit de volledige expressie op één regel te schrijven. Dit is om de imperatieve code te onderscheiden van het functionele.

// ✔️ OK
if a then
    ()

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

Als een van de expressies meerdere regels is, moet elke voorwaardelijke vertakking meerdere regels zijn.

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

Meerdere voorwaarden met elif en else worden ingesprongen in hetzelfde bereik als wanneer if ze de regels van de ene regelexpressies if/then/else volgen.

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

Als een van de voorwaarden of expressies meerdere regels is, is de volledige if/then/else expressie meerdere regels:

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

Als een voorwaarde meerdere regels heeft of de standaardtolerantie van de regel met één regel overschrijdt, moet de voorwaardeexpressie één inspringing en een nieuwe regel gebruiken. Het if en then trefwoord moeten worden uitgelijnd bij het inkapselen van de expressie voor lange voorwaarden.

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

Het is echter een betere stijl om lange voorwaarden te herstructureren naar een let binding of afzonderlijke functie:

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

if performAction then
    e1
else
    e2

Samenvoegingsuitdrukkingen opmaken

Het toepassen van gediscrimineerde samenvoegcases volgt dezelfde regels als functie- en methodetoepassingen. Dat wil gezegd, omdat de naam hoofdletters heeft, verwijderen code-formatters een spatie voor een tuple:

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

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

Net als bij functietoepassingen moeten constructies die over meerdere lijnen worden gesplitst, inspringing gebruiken:

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

Lijst- en matrixexpressies opmaken

Schrijven x :: l met spaties rond de :: operator (:: is een infix-operator, dus omgeven door spaties).

Lijst en matrices die op één regel zijn gedeclareerd, moeten een spatie hebben na de haak openen en vóór de haak sluiten:

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

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

Gebruik altijd ten minste één spatie tussen twee afzonderlijke accolades. Laat bijvoorbeeld een spatie tussen een [ en een {.

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

Dezelfde richtlijn geldt voor lijsten of matrices van tuples.

Lijsten en matrices die over meerdere regels zijn gesplitst, volgen een vergelijkbare regel als records:

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

Net als bij records maken het declareren van de haakjes openen en sluiten op hun eigen regel het verplaatsen van code en het doorschakelen naar functies eenvoudiger:

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

Als een lijst- of matrixexpressie de rechterkant van een binding is, kunt u het beste stijl gebruiken 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 |] 
|]

Als een lijst- of matrixexpressie echter niet de rechterkant van een binding is, bijvoorbeeld wanneer deze zich in een andere lijst of matrix bevindt, moeten de vierkante haken op hun eigen regels worden geplaatst:

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

Dezelfde regel geldt voor recordtypen in matrices/lijsten:

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

Wanneer u matrices en lijsten programmatisch genereert, geeft u de voorkeur -> aan do ... yield wanneer een waarde altijd wordt gegenereerd:

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

Oudere versies van F# moeten worden opgegeven yield in situaties waarin gegevens voorwaardelijk kunnen worden gegenereerd, of er kunnen opeenvolgende expressies worden geëvalueerd. Laat deze yield trefwoorden liever weg tenzij u moet compileren met een oudere F#-taalversie:

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

In sommige gevallen do...yield kan dit helpen bij de leesbaarheid. Deze gevallen, hoewel subjectief, moeten in overweging worden genomen.

Recordexpressies opmaken

Korte records kunnen in één regel worden geschreven:

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

Records die langer zijn, moeten nieuwe regels gebruiken voor labels:

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

Opmaakstijlen voor vierkante haken met meerdere regels

Voor records die meerdere regels omvatten, zijn er drie veelgebruikte opmaakstijlen: Cramped, Aligneden Stroustrup. De Cramped stijl is de standaardstijl voor F#-code, omdat deze de voorkeur geeft aan stijlen waarmee de compiler eenvoudig code kan parseren. Stroustrup Met beide Aligned stijlen kunt u leden eenvoudiger ordenen, wat leidt tot code die gemakkelijker te herstructureren is, met het nadeel dat bepaalde situaties mogelijk iets uitgebreidere code vereisen.

  • Cramped: de historische standaard en de standaard F#-recordindeling. Haakjes openen gaan op dezelfde regel als het eerste lid, haak sluiten op dezelfde regel als het laatste lid.

    let rainbow = 
        { Boss1 = "Jeffrey"
          Boss2 = "Jeffrey"
          Boss3 = "Jeffrey"
          Lackeys = [ "Zippy"; "George"; "Bungle" ] }
    
  • Aligned: Haakjes krijgen elk een eigen lijn, uitgelijnd op dezelfde kolom.

    let rainbow =
        {
            Boss1 = "Jeffrey"
            Boss2 = "Jeffrey"
            Boss3 = "Jeffrey"
            Lackeys = ["Zippy"; "George"; "Bungle"]
        }
    
  • Stroustrup: Het openen van de haak gaat op dezelfde lijn als de binding, het sluiten van een haak krijgt een eigen lijn.

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

Dezelfde opmaakstijlregels zijn van toepassing op lijst- en matrixelementen.

Recordexpressies voor kopiëren en bijwerken opmaken

Een expressie voor het kopiëren en bijwerken van records is nog steeds een record, dus vergelijkbare richtlijnen zijn van toepassing.

Korte expressies kunnen op één regel passen:

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

Langere expressies moeten nieuwe regels en opmaak gebruiken op basis van een van de bovenstaande conventies:

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

Opmerking: als u Stroustrup stijl gebruikt voor expressies voor kopiëren en bijwerken, moet u leden verder laten inspringen dan de gekopieerde recordnaam:

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

Opmaakpatroonovereenkomst

Gebruik een | voor elke component van een overeenkomst zonder inspringing. Als de expressie kort is, kunt u overwegen om één regel te gebruiken als elke subexpressie ook eenvoudig is.

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

Als de expressie rechts van de overeenkomende pijl voor het patroon te groot is, verplaatst u deze naar de volgende regel, ingesprongen één stap van de match/|.

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

Net als bij grote als voorwaarden, als een overeenkomstexpressie meerdere regels is of de standaardtolerantie van de regel met één regel overschrijdt, moet de overeenkomstexpressie één inspringing en een nieuwe regel gebruiken. Het match en with trefwoord moeten worden uitgelijnd bij het inkapselen van de lange overeenkomstexpressie.

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

Het is echter een betere stijl om lange overeenkomstexpressies te herstructureren naar een letbinding of afzonderlijke functie:

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

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

Het uitlijnen van de pijlen van een patroonovereenkomst moet worden vermeden.

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

Patroonkoppeling dat is geïntroduceerd met behulp van het trefwoord function , moet één niveau laten inspringen vanaf het begin van de vorige regel:

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

Het gebruik van function functies die zijn gedefinieerd door let of let rec in het algemeen moeten worden vermeden ten gunste van een match. Indien gebruikt, moeten de patroonregels overeenkomen met het trefwoord 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

Opmaak proberen/met expressies

Patroonkoppeling voor het uitzonderingstype moet worden ingesprongen op hetzelfde niveau als 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"

Voeg een | voor elke component toe, behalve wanneer er slechts één component is:

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

Benoemde argumenten opmaken

Benoemde argumenten moeten spaties hebben rond het =volgende:

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

Wanneer patroonkoppelingen worden gebruikt met gediscrimineerde samenvoegingen, worden benoemde patronen bijvoorbeeld op dezelfde manier opgemaakt.

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

Mutatieexpressies opmaken

Mutatie-expressies location <- expr worden normaal opgemaakt op één regel. Als opmaak met meerdere regels is vereist, plaatst u de expressie aan de rechterkant op een nieuwe regel.

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

Objectexpressies opmaken

Objectexpressieleden moeten worden uitgelijnd met member inspringing op één niveau.

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

U kunt er ook de voorkeur aan geven om stijl te gebruiken 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)
}

Lege typedefinities kunnen op één regel worden opgemaakt:

type AnEmptyType = class end

Ongeacht de gekozen paginabreedte = class end moet altijd op dezelfde lijn staan.

Index-/segmentexpressies opmaken

Indexexpressies mogen geen spaties bevatten rond de vierkante haken openen en sluiten.

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

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

Dit geldt ook voor de oudere expr.[idx] syntaxis.

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

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

Expressies tussen aanhaaknotaties

De scheidingstekensymbolen (<@ , @>, <@@, ) @@>moeten op afzonderlijke regels worden geplaatst als de aanhalingstekenexpressie een expressie met meerdere regels is.

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

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

In expressies met één regel moeten de scheidingstekens op dezelfde regel als de expressie zelf worden geplaatst.

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

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

Gekoppelde expressies opmaken

Wanneer ketenexpressies (functietoepassingen met elkaar verbonden) .lang zijn, plaatst u elke toepassing aanroep op de volgende regel. Laat de volgende koppelingen in de keten met één niveau inspringen na de voorloopkoppeling.

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

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

De voorloopkoppeling kan uit meerdere koppelingen bestaan als het eenvoudige id's zijn. Bijvoorbeeld de toevoeging van een volledig gekwalificeerde naamruimte.

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

Volgende koppelingen moeten ook eenvoudige id's bevatten.

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

Wanneer de argumenten in een functietoepassing niet op de rest van de regel passen, plaatst u elk argument op de volgende regel.

// ✔️ OK
WebHostBuilder()
    .UseKestrel()
    .UseUrls("http://*:5000/")
    .UseCustomCode(
        longArgumentOne,
        longArgumentTwo,
        longArgumentThree,
        longArgumentFour
    )
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseStartup<Startup>()
    .Build()

// ✔️ OK
Cache.providedTypes
    .GetOrAdd(cacheKey, addCache)
    .Value

// ❌ Not OK, formatting tools will reformat to the above
Cache
    .providedTypes
    .GetOrAdd(
        cacheKey,
        addCache
    )
    .Value

Lambda-argumenten binnen een functietoepassing moeten beginnen op dezelfde regel als de opening (.

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

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

Opmaakdeclaraties

In deze sectie worden opmaakdeclaraties van verschillende soorten besproken.

Lege regels tussen declaraties toevoegen

Afzonderlijke functie- en klassedefinities op het hoogste niveau met één lege regel. Voorbeeld:

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

Als een constructie xml-documentopmerkingen bevat, voegt u een lege regel toe vóór de opmerking.

// ✔️ OK

/// This is a function
let thisFunction() =
    1 + 1

/// This is another function, note the blank line before this line
let thisFunction() =
    1 + 1

Let- en liddeclaraties opmaken

Bij het opmaken let en member declaraties gaat de rechterzijde van een binding meestal op één regel of (als deze te lang is) op een nieuwe regel ingesprongen één niveau.

De volgende voorbeelden zijn bijvoorbeeld compatibel:

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

Deze zijn niet-compatibel:

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

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

Instantiëringen van recordtypen kunnen ook de vierkante haken op hun eigen regels plaatsen:

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

U kunt ook liever stijl gebruiken Stroustrup , met de opening { op dezelfde regel als de bindingsnaam:

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

Afzonderlijke leden met één lege regel en één document en voeg een opmerking bij de documentatie toe:

// ✔️ OK

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

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

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

Extra lege lijnen kunnen (spaarzaam) worden gebruikt om groepen gerelateerde functies te scheiden. Lege regels kunnen worden weggelaten tussen een aantal gerelateerde one-liners (bijvoorbeeld een set dummy-implementaties). Gebruik lege regels in functies, spaarzaam, om logische secties aan te geven.

Functie- en lidargumenten opmaken

Wanneer u een functie definieert, gebruikt u witruimte rond elk argument.

// ✔️ OK
let myFun (a: decimal) (b: int) c = a + b + c

// ❌ Not OK, code formatters will reformat to the above by default
let myFunBad (a:decimal)(b:int)c = a + b + c

Als u een lange functiedefinitie hebt, plaatst u de parameters op nieuwe regels en laat u deze inspringen zodat deze overeenkomen met het inspringingsniveau van de volgende parameter.

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

Dit geldt ook voor leden, constructors en parameters met behulp van tuples:

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

Als de parameters worden gecureerd, plaatst u het = teken samen met een retourtype op een nieuwe regel:

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

Dit is een manier om te lange lijnen te voorkomen (in het geval van een retourtype kan lange naam hebben) en minder lijnschade bij het toevoegen van parameters.

Declaraties van opmaakoperator

U kunt eventueel witruimte gebruiken om een operatordefinitie te omsluiten:

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

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

Voor elke aangepaste operator die begint met * en die meer dan één teken heeft, moet u een witruimte toevoegen aan het begin van de definitie om dubbelzinnigheid van een compiler te voorkomen. Daarom raden we u aan om de definities van alle operators te omsluiten met één witruimteteken.

Recorddeclaraties opmaken

Voor recorddeclaraties moet u de definitie van het { type standaard met vier spaties laten inspringen, de lijst met etiketten op dezelfde regel starten en leden, indien van toepassing, uitlijnen met het { token:

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

Het is ook gebruikelijk om haakjes op hun eigen lijn te plaatsen, waarbij labels worden ingesprongen door een extra vier spaties:

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

U kunt ook het { einde van de eerste regel van de typedefinitie (Stroustrup stijl) plaatsen:

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

Als er extra leden nodig zijn, kunt u het volgende niet gebruiken with/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
  
// ✔️ 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

De uitzondering op deze stijlregel is als u records opmaken op basis van de Stroustrup stijl. In dit geval is het with trefwoord vereist vanwege compilerregels als u een interface wilt implementeren of extra leden wilt toevoegen:

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

Wanneer XML-documentatie wordt toegevoegd voor recordvelden Aligned of Stroustrup stijl de voorkeur heeft, en er moet extra witruimte worden toegevoegd tussen leden:

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

Het openen van het token op een nieuwe regel plaatsen en het afsluitende token op een nieuwe regel verdient de voorkeur als u interface-implementaties of leden op de record declareren:

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

Dezelfde regels zijn van toepassing op anonieme recordtypealiassen.

Gediscrimineerde samenvoegingsdeclaraties opmaken

Voor gediscrimineerde samenvoegingsdeclaraties laat u inspringen | in de typedefinitie met vier spaties:

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

Wanneer er één korte samenvoeging is, kunt u de voorloop |weglaten.

// ✔️ OK
type Address = Address of string

Voor een langere of meerregelige samenvoeging houdt u het | en plaatst u elk samenvoegveld op een nieuwe regel, met de scheiding * aan het einde van elke regel.

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

Wanneer er documentatieopmerkingen worden toegevoegd, gebruikt u een lege regel voor elke /// opmerking.

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

Letterlijke declaraties opmaken

Letterlijke F#-waarden met behulp van het Literal kenmerk moeten het kenmerk op een eigen regel plaatsen en PascalCase-naamgeving gebruiken:

// ✔️ OK

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

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

Vermijd het plaatsen van het kenmerk op dezelfde regel als de waarde.

Declaraties van opmaakmodules

Code in een lokale module moet worden ingesprongen ten opzichte van de module, maar code in een module op het hoogste niveau mag niet worden ingesprongen. Naamruimte-elementen hoeven niet te worden ingesprongen.

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

Opmaak do declaraties

In typedeclaraties, moduledeclaraties en berekeningsexpressies is het gebruik van do of do! soms vereist voor nevenbewerkingen. Wanneer deze meerdere regels omvatten, gebruikt u inspringing en een nieuwe regel om de inspringing consistent te houden met let/let!. Hier volgt een voorbeeld van het gebruik do in een klasse:

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

Hier volgt een voorbeeld van do! het gebruik van twee spaties van inspringing (omdat do! er bij benadering toevallig geen verschil is tussen de benaderingen bij het gebruik van vier spaties van inspringing):

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

Bewerkingen voor berekeningsexpressies opmaken

Bij het maken van aangepaste bewerkingen voor berekeningsexpressies wordt aanbevolen om naamgeving van camelCase te gebruiken:

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

Het domein dat wordt gemodelleerd, moet uiteindelijk de naamconventie aandrijven. Als het idiomatisch is om een andere conventie te gebruiken, moet die conventie in plaats daarvan worden gebruikt.

Als de retourwaarde van een expressie een berekeningsexpressie is, geeft u de voorkeur aan de trefwoordnaam van de rekenexpressie op een eigen regel te plaatsen:

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

U kunt ook liever de rekenexpressie op dezelfde regel plaatsen als de bindingsnaam:

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

Ongeacht uw voorkeur, moet u proberen consistent te blijven in uw codebasis. Met formatters kunt u deze voorkeur opgeven om consistent te blijven.

Opmaaktypen en aantekeningen typen

In deze sectie worden opmaaktypen en typeaantekeningen besproken. Dit omvat het opmaken van handtekeningbestanden met de .fsi extensie.

Voor typen geeft u de voorkeur aan voorvoegselsyntaxis voor generieken (Foo<T>), met enkele specifieke uitzonderingen

F# staat zowel de stijl van het schrijven van algemene typen toe (bijvoorbeeld int list) als de voorvoegselstijl (bijvoorbeeld list<int>). De stijl Voorvoegsel kan alleen worden gebruikt met één typeargument. Geef altijd de voorkeur aan de .NET-stijl, met uitzondering van vijf specifieke typen:

  1. Gebruik voor F#-lijsten het postfixformulier: int list in plaats list<int>van .
  2. Gebruik voor F#-opties het voorvoegselformulier: int option in plaats option<int>van .
  3. Voor F#-waardeopties gebruikt u het formulier voor het voorvoegsel: int voption in plaats voption<int>van .
  4. Gebruik voor F#-matrices het postfix-formulier: int array in plaats array<int> van of int[].
  5. Gebruik voor verwijzingscellen int ref in plaats ref<int> van of Ref<int>.

Gebruik voor alle andere typen het voorvoegselformulier.

Functietypen opmaken

Wanneer u de handtekening van een functie definieert, gebruikt u witruimte rond het -> symbool:

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

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

Notaties voor waarde en argumenttype opmaken

Wanneer u waarden of argumenten definieert met typeaantekeningen, gebruikt u witruimte na het : symbool, maar niet vóór:

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

Aantekeningen van meerdere regels opmaken

Wanneer een typeaantekening lang of meerdere regels is, plaatst u deze op de volgende regel, ingesprongen met één niveau.

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

Voor inline anonieme recordtypen kunt u ook stijl gebruiken Stroustrup :

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

Aantekeningen van retourtype opmaken

Gebruik witruimte vóór en na het : symbool in functie of lid:

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

Opmaaktypen in handtekeningen

Bij het schrijven van volledige functietypen in handtekeningen, is het soms nodig om de argumenten over meerdere regels te splitsen. Het retourtype wordt altijd ingesprongen.

Voor een tupledfunctie worden de argumenten gescheiden door *, geplaatst aan het einde van elke regel.

Denk bijvoorbeeld aan een functie met de volgende implementatie:

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

In het bijbehorende handtekeningbestand (.fsi extensie) kan de functie als volgt worden opgemaakt wanneer opmaak met meerdere regels is vereist:

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

Overweeg ook een curriede functie:

let SampleCurriedFunction arg1 arg2 arg3 arg4 = ...

In het bijbehorende handtekeningbestand worden de -> waarden aan het einde van elke regel geplaatst:

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

Denk ook aan een functie die een combinatie van gecureerde en tupled argumenten accepteert:

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

In het bijbehorende handtekeningbestand worden de typen voorafgegaan door een tuple ingesprongen

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

Dezelfde regels gelden voor leden in typehandtekeningen:

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

Expliciete algemene typeargumenten en beperkingen opmaken

De onderstaande richtlijnen zijn van toepassing op functiedefinities, liddefinities, typedefinities en functietoepassingen.

Gebruik algemene typeargumenten en beperkingen op één regel als deze niet te lang is:

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

Als zowel algemene typeargumenten/beperkingen als functieparameters niet passen, maar de typeparameters/beperkingen alleen doen, plaatst u de parameters op nieuwe regels:

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

Als de typeparameters of beperkingen te lang zijn, kunt u deze verbreken en uitlijnen zoals hieronder wordt weergegeven. Behoud de lijst met typeparameters op dezelfde regel als de functie, ongeacht de lengte. Voor beperkingen plaatst when u op de eerste regel en houdt u elke beperking op één regel, ongeacht de lengte. Plaats > aan het einde van de laatste regel. De beperkingen met één niveau laten inspringen.

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

Als de typeparameters/beperkingen zijn opgesplitst, maar er geen normale functieparameters zijn, plaatst u de = op een nieuwe regel, ongeacht:

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

Dezelfde regels zijn van toepassing op functietoepassingen:

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

Overname opmaken

De argumenten voor de basisklasseconstructor worden weergegeven in de argumentenlijst in de inherit component. Plaats de inherit component op een nieuwe regel, ingesprongen met één niveau.

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)

Wanneer de constructor lang of meerdere regels is, plaatst u deze op de volgende regel, ingesprongen door één niveau.
Maak deze multilineconstructor op volgens de regels van multiline-functietoepassingen.

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

De primaire constructor opmaken

In standaardopmaakconventies wordt geen spatie toegevoegd tussen de typenaam en de haakjes voor de primaire constructor.

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

Meerdere constructors

Wanneer de inherit component deel uitmaakt van een record, plaatst u deze op dezelfde regel als deze kort is. En plaats deze op de volgende regel, ingesprongen met één niveau, als het lang of meerdere regels is.

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 }

Opmaakkenmerken

Kenmerken worden boven een constructie geplaatst:

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

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

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

Deze moeten na alle XML-documentatie gaan:

// ✔️ OK

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

Opmaakkenmerken voor parameters

Kenmerken kunnen ook op parameters worden geplaatst. In dit geval plaatst u deze op dezelfde regel als de parameter en vóór de naam:

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

Meerdere kenmerken opmaken

Wanneer meerdere kenmerken worden toegepast op een constructie die geen parameter is, plaatst u elk kenmerk op een afzonderlijke regel:

// ✔️ OK

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

Wanneer deze worden toegepast op een parameter, plaatst u kenmerken op dezelfde regel en scheidt u deze met een ; scheidingsteken.

Erkenningen

Deze richtlijnen zijn gebaseerd op een uitgebreide handleiding voor F#-opmaakconventies door Anh-Dung Phan.